Util: Use FreeType's stroker instead of an SDF

This commit is contained in:
Vicki Pfau 2025-06-08 23:10:45 -07:00
parent 7fa4acf1c7
commit 9a4c4e720f
4 changed files with 65 additions and 56 deletions

View File

@ -171,7 +171,6 @@ 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,28 +6,36 @@ 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:setStrokeWidth(1)
state.painter:setFillColor(0xFFFFFFFF)
state.painter:drawText("Top\nleft", 0, 0, C.ALIGN.TOP | C.ALIGN.LEFT)
state.painter:setStrokeWidth(2)
state.painter:setFillColor(0xFF00FFFF)
state.painter:drawText("Top\ncenter", state.width / 2, 0, C.ALIGN.TOP | C.ALIGN.HCENTER)
state.painter:setStrokeWidth(1)
state.painter:setFillColor(0xFFFF00FF)
state.painter:drawText("Top\nright", state.width, 0, C.ALIGN.TOP | C.ALIGN.RIGHT)
state.painter:setStrokeWidth(2)
state.painter:setFillColor(0xFFFFFF00)
state.painter:drawText("Center\nleft", 0, state.height / 2, C.ALIGN.VCENTER | C.ALIGN.LEFT)
state.painter:setStrokeWidth(3)
state.painter:setFillColor(0xFFFF0000)
state.painter:drawText("Center\ncenter", state.width / 2, state.height / 2, C.ALIGN.VCENTER | C.ALIGN.HCENTER)
state.painter:setStrokeWidth(2)
state.painter:setFillColor(0xFF00FF00)
state.painter:drawText("Center\nright", state.width, state.height / 2, C.ALIGN.VCENTER | C.ALIGN.RIGHT)
state.painter:setStrokeWidth(1)
state.painter:setFillColor(0xFF0000FF)
state.painter:drawText("Bottom\nleft", 0, state.height, C.ALIGN.BOTTOM | C.ALIGN.LEFT)
state.painter:setStrokeWidth(2)
state.painter:setFillColor(0xFF808080)
state.painter:drawText("Bottom\ncenter", state.width / 2, state.height, C.ALIGN.BOTTOM | C.ALIGN.HCENTER)
state.painter:setStrokeWidth(1)
state.painter:setStrokeColor(0xFFFFFFFF)
state.painter:setFillColor(0xFF000000)
state.painter:drawText("Bottom\nright", state.width, state.height, C.ALIGN.BOTTOM | C.ALIGN.RIGHT)

View File

@ -904,40 +904,6 @@ 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

@ -12,6 +12,7 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MODULE_H
#include FT_STROKER_H
#define DPI 100
@ -20,6 +21,7 @@ static FT_Library library;
struct mFont {
FT_Face face;
FT_Stroker stroker;
unsigned emHeight;
};
@ -43,10 +45,6 @@ struct mFont* mFontOpen(const char* path) {
if (FT_Init_FreeType(&library)) {
return NULL;
}
#if FREETYPE_MAJOR >= 2 && FREETYPE_MINOR >= 11
FT_Int spread = 5;
FT_Property_Set(library, "sdf", "spread", &spread);
#endif
}
FT_Face face;
@ -54,14 +52,22 @@ struct mFont* mFontOpen(const char* path) {
return NULL;
}
FT_Stroker stroker;
if (FT_Stroker_New(library, &stroker)) {
FT_Done_Face(face);
return NULL;
}
struct mFont* font = calloc(1, sizeof(*font));
font->face = face;
font->stroker = stroker;
mFontSetSize(font, 8 << mFONT_FRACT_BITS);
return font;
}
void mFontDestroy(struct mFont* font) {
FT_Done_Face(font->face);
FT_Stroker_Done(font->stroker);
free(font);
size_t opened = --libraryOpen;
@ -134,7 +140,7 @@ void mFontTextBoxSize(struct mFont* font, const char* text, int lineSpacing, str
out->height = height;
}
static const char* mPainterDrawTextRun(struct mPainter* painter, const char* text, int x, int y, enum mAlignment alignment, uint8_t sdfThreshold) {
static const char* mPainterDrawTextRun(struct mPainter* painter, const char* text, int x, int y, enum mAlignment alignment, bool stroke) {
FT_Face face = painter->font->face;
struct mTextRunMetrics metrics;
mFontRunMetrics(painter->font, text, &metrics);
@ -151,7 +157,7 @@ static const char* mPainterDrawTextRun(struct mPainter* painter, const char* tex
break;
}
uint32_t lastGlyph = 0;
uint32_t lastCodepoint = 0;
while (*text) {
uint32_t codepoint = utf8Char((const char**) &text, NULL);
@ -160,32 +166,62 @@ static const char* mPainterDrawTextRun(struct mPainter* painter, const char* tex
}
if (FT_Load_Char(face, codepoint, FT_LOAD_DEFAULT)) {
lastGlyph = 0;
lastCodepoint = 0;
continue;
}
if (FT_Render_Glyph(face->glyph, sdfThreshold ? FT_RENDER_MODE_SDF : FT_RENDER_MODE_NORMAL)) {
lastGlyph = 0;
continue;
FT_Bitmap* bitmap;
FT_Glyph glyph = NULL;
int top;
int left;
if (stroke) {
FT_Stroker_Set(painter->font->stroker, painter->strokeWidth << mFONT_FRACT_BITS, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
FT_Get_Glyph(face->glyph, &glyph);
if (painter->fill) {
FT_Glyph_Stroke(&glyph, painter->font->stroker, 1);
} else {
FT_Glyph_StrokeBorder(&glyph, painter->font->stroker, 0, 1);
}
if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1)) {
FT_Done_Glyph(glyph);
lastCodepoint = 0;
continue;
}
FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) glyph;
bitmap = &bitmapGlyph->bitmap;
top = bitmapGlyph->top;
left = bitmapGlyph->left;
} else {
if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) {
lastCodepoint = 0;
continue;
}
bitmap = &face->glyph->bitmap;
top = face->glyph->bitmap_top;
left = face->glyph->bitmap_left;
}
struct mImage image;
_makeTemporaryImage(&image, &face->glyph->bitmap);
_makeTemporaryImage(&image, bitmap);
FT_Vector kerning = {0};
FT_Get_Kerning(face, lastGlyph, codepoint, FT_KERNING_DEFAULT, &kerning);
FT_Get_Kerning(face, lastCodepoint, codepoint, FT_KERNING_DEFAULT, &kerning);
x += kerning.x;
y += kerning.y;
if (sdfThreshold) {
mPainterDrawSDFMask(painter, &image, (x >> 6) + face->glyph->bitmap_left, (y >> 6) - face->glyph->bitmap_top, 0x70);
} else {
mPainterDrawMask(painter, &image, (x >> 6) + face->glyph->bitmap_left, (y >> 6) - face->glyph->bitmap_top);
}
mPainterDrawMask(painter, &image, (x >> mFONT_FRACT_BITS) + left, (y >> mFONT_FRACT_BITS) - top);
x += face->glyph->advance.x;
y += face->glyph->advance.y;
lastGlyph = codepoint;
if (glyph) {
FT_Done_Glyph(glyph);
}
lastCodepoint = codepoint;
}
return NULL;
@ -221,7 +257,7 @@ void mPainterDrawText(struct mPainter* painter, const char* text, int x, int y,
uint32_t fillColor = painter->fillColor;
painter->fillColor = painter->strokeColor;
do {
ltext = mPainterDrawTextRun(painter, ltext, x, yy, alignment, 0x70);
ltext = mPainterDrawTextRun(painter, ltext, x + painter->strokeWidth , yy, alignment, true);
yy += face->size->metrics.height;
} while (ltext);
@ -230,7 +266,7 @@ void mPainterDrawText(struct mPainter* painter, const char* text, int x, int y,
#endif
do {
text = mPainterDrawTextRun(painter, text, x, y, alignment, 0);
text = mPainterDrawTextRun(painter, text, x, y, alignment, false);
y += face->size->metrics.height;
} while (text);
}