Util: Add somewhat hacky SDF masking for outlined text rendering

This commit is contained in:
Vicki Pfau 2025-06-06 01:55:10 -07:00
parent 41d25de69f
commit c4d3cf7de5
4 changed files with 80 additions and 2 deletions

View File

@ -159,6 +159,7 @@ void mPainterDrawRectangle(struct mPainter*, int x, int y, int width, int height
void mPainterDrawLine(struct mPainter*, int x1, int y1, int x2, int y2);
void mPainterDrawCircle(struct mPainter*, int x, int y, int diameter);
void mPainterDrawMask(struct mPainter*, const struct mImage* mask, int x, int y);
void mPainterDrawSDFMask(struct mPainter*, const struct mImage* mask, int x, int y, uint8_t upper);
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to);
uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to);

View File

@ -6,6 +6,8 @@ state.painter = image.newPainter(state.overlay.image)
state.painter:loadFont(script.dir .. "/SourceSans3-Regular.otf")
state.painter:setFontSize(9)
state.painter:setFill(true)
state.painter:setStrokeWidth(1)
state.painter:setStrokeColor(0xFF000000)
state.painter:setBlend(true)
state.painter:setFillColor(0xFFFFFFFF)
@ -24,8 +26,9 @@ state.painter:drawText("Center right", state.width, state.height / 2, C.ALIGN.VC
state.painter:setFillColor(0xFF0000FF)
state.painter:drawText("Bottom left", 0, state.height, C.ALIGN.BOTTOM | C.ALIGN.LEFT)
state.painter:setFillColor(0xFF000000)
state.painter:drawText("Bottom center", state.width / 2, state.height, C.ALIGN.BOTTOM | C.ALIGN.HCENTER)
state.painter:setFillColor(0xFF808080)
state.painter:drawText("Bottom center", state.width / 2, state.height, C.ALIGN.BOTTOM | C.ALIGN.HCENTER)
state.painter:setStrokeColor(0xFFFFFFFF)
state.painter:setFillColor(0xFF000000)
state.painter:drawText("Bottom right", state.width, state.height, C.ALIGN.BOTTOM | C.ALIGN.RIGHT)
state.overlay:update()

View File

@ -904,6 +904,40 @@ void mPainterDrawMask(struct mPainter* painter, const struct mImage* mask, int x
}
}
void mPainterDrawSDFMask(struct mPainter* painter, const struct mImage* mask, int x, int y, uint8_t upper) {
if (mask->format != mCOLOR_L8) {
return;
}
COMPOSITE_BOUNDS_INIT(mask, painter->backing);
for (y = 0; y < srcRect.height; ++y) {
uintptr_t dstPixel = (uintptr_t) PIXEL(painter->backing, dstStartX, dstStartY + y);
uintptr_t maskPixel = (uintptr_t) PIXEL(mask, srcStartX, srcStartY + y);
for (x = 0; x < srcRect.width; ++x, dstPixel += painter->backing->depth, maskPixel += mask->depth) {
uint32_t color;
GET_PIXEL(color, maskPixel, mask->depth);
if (color >= upper) {
color = painter->fillColor;
} else if (color + 0x10 > upper) {
// TODO: Make this spread customizable without too much perf hit
color = ((color + 0x10 - upper) * 0x10) << 24;
color |= painter->fillColor & 0x00FFFFFF;
} else {
continue;
}
if (painter->blend || painter->fillColor < 0xFF000000) {
uint32_t current;
GET_PIXEL(current, dstPixel, painter->backing->depth);
current = mColorConvert(current, painter->backing->format, mCOLOR_ARGB8);
color = mColorMixARGB8(color, current);
}
color = mColorConvert(color, mCOLOR_ARGB8, painter->backing->format);
PUT_PIXEL(color, dstPixel, painter->backing->depth);
}
}
}
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) {
if (from == to) {
return color;

View File

@ -11,6 +11,7 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MODULE_H
#define DPI 100
@ -42,6 +43,9 @@ struct mFont* mFontOpen(const char* path) {
if (FT_Init_FreeType(&library)) {
return NULL;
}
FT_Int spread = 5;
FT_Property_Set(library, "sdf", "spread", &spread);
}
FT_Face face;
@ -131,6 +135,42 @@ void mPainterDrawText(struct mPainter* painter, const char* text, int x, int y,
break;
}
if (painter->strokeWidth) {
int xx = x;
int yy = y;
const char* ltext = text;
uint32_t fillColor = painter->fillColor;
painter->fillColor = painter->strokeColor;
while (*ltext) {
uint32_t glyph = utf8Char((const char**) &ltext, NULL);
if (FT_Load_Char(face, glyph, FT_LOAD_DEFAULT)) {
continue;
}
if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF)) {
continue;
}
struct mImage image;
_makeTemporaryImage(&image, &face->glyph->bitmap);
FT_Vector kerning = {0};
FT_Get_Kerning(face, lastGlyph, glyph, FT_KERNING_DEFAULT, &kerning);
xx += kerning.x;
yy += kerning.y;
mPainterDrawSDFMask(painter, &image, (xx >> 6) + face->glyph->bitmap_left, (yy >> 6) - face->glyph->bitmap_top, 0x70);
xx += face->glyph->advance.x;
yy += face->glyph->advance.y;
lastGlyph = glyph;
}
painter->fillColor = fillColor;
lastGlyph = 0;
}
while (*text) {
uint32_t glyph = utf8Char((const char**) &text, NULL);