Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font Dilation (font outline) #640

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
30 changes: 30 additions & 0 deletions example/demo.c
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,35 @@ void drawSlider(NVGcontext* vg, float pos, float x, float y, float w, float h)
nvgRestore(vg);
}

void drawFancyText(NVGcontext* vg, float x, float y){
nvgFontSize(vg, 30.0f);
nvgFontFace(vg, "sans-bold");
nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);

nvgFontBlur(vg, 10);
nvgFillColor(vg, nvgRGB(255, 0, 102));
nvgText(vg, x, y, "Font Blur", NULL);
nvgFontBlur(vg,0);
nvgFillColor(vg, nvgRGB(255,255,255));
nvgText(vg, x, y, "Font Blur", NULL);

nvgFontDilate(vg, 2);
nvgFillColor(vg, nvgRGB(255, 0, 102));
nvgText(vg, x, y+40, "Font Outline", NULL);
nvgFontDilate(vg,0);
nvgFillColor(vg, nvgRGB(255,255,255));
nvgText(vg, x, y+40, "Font Outline", NULL);

nvgFontDilate(vg, 3); // Dilate will always be applied before blur
nvgFontBlur(vg, 2);
nvgFillColor(vg, nvgRGB(255, 0, 102));
nvgText(vg, x, y+80, "Font Blur Outline", NULL);
nvgFontDilate(vg,0);
nvgFontBlur(vg,0);
nvgFillColor(vg, nvgRGB(255,255,255));
nvgText(vg, x, y+80, "Font Blur Outline", NULL);
}

void drawEyes(NVGcontext* vg, float x, float y, float w, float h, float mx, float my, float t)
{
NVGpaint gloss, bg;
Expand Down Expand Up @@ -1068,6 +1097,7 @@ void renderDemo(NVGcontext* vg, float mx, float my, float width, float height,
float x,y,popy;

drawEyes(vg, width - 250, 50, 150, 100, mx, my, t);
drawFancyText(vg, width - 175, 190);
drawParagraph(vg, width - 450, 50, 150, 100, mx, my);
drawGraph(vg, 0, height/2, width, height/2, t);
drawColorwheel(vg, width - 300, height - 300, 250.0f, 250.0f, t);
Expand Down
161 changes: 151 additions & 10 deletions src/fontstash.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ typedef struct FONSquad FONSquad;
struct FONStextIter {
float x, y, nextx, nexty, scale, spacing;
unsigned int codepoint;
short isize, iblur;
short isize, iblur, idilate;
struct FONSfont* font;
int prevGlyphIndex;
const char* str;
Expand Down Expand Up @@ -116,6 +116,7 @@ void fonsSetSize(FONScontext* s, float size);
void fonsSetColor(FONScontext* s, unsigned int color);
void fonsSetSpacing(FONScontext* s, float spacing);
void fonsSetBlur(FONScontext* s, float blur);
void fonsSetDilate(FONScontext* s, float dilate);
void fonsSetAlign(FONScontext* s, int align);
void fonsSetFont(FONScontext* s, int font);

Expand Down Expand Up @@ -225,7 +226,7 @@ struct FONSglyph
unsigned int codepoint;
int index;
int next;
short size, blur;
short size, blur, dilate;
short x0,y0,x1,y1;
short xadv,xoff,yoff;
};
Expand Down Expand Up @@ -257,6 +258,7 @@ struct FONSstate
float size;
unsigned int color;
float blur;
float dilate;
float spacing;
};
typedef struct FONSstate FONSstate;
Expand Down Expand Up @@ -834,6 +836,10 @@ void fonsSetBlur(FONScontext* stash, float blur)
{
fons__getState(stash)->blur = blur;
}
void fonsSetDilate(FONScontext* stash, float dilate)
{
fons__getState(stash)->dilate = dilate;
}

void fonsSetAlign(FONScontext* stash, int align)
{
Expand Down Expand Up @@ -874,6 +880,7 @@ void fonsClearState(FONScontext* stash)
state->color = 0xffffffff;
state->font = 0;
state->blur = 0;
state->dilate= 0;
state->spacing = 0;
state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE;
}
Expand Down Expand Up @@ -1069,12 +1076,131 @@ static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int
fons__blurCols(dst, w, h, dstStride, alpha);
fons__blurRows(dst, w, h, dstStride, alpha);
fons__blurCols(dst, w, h, dstStride, alpha);
// fons__blurrows(dst, w, h, dstStride, alpha);
// fons__blurcols(dst, w, h, dstStride, alpha);
}

static void fons__maxRows(unsigned char* dst, int w, int h, int dstStride)
{
int x, y;
unsigned char prev, current;
for (x = 0; x < w; x++) {
prev=dst[0];
for (y = dstStride; y < h*dstStride; y += dstStride) {
current=dst[y];
if(prev > current){
dst[y]=prev;
}
prev=current;
}
for (y = (h-2)*dstStride; y >= 0; y -= dstStride) {
current=dst[y];
if(prev > current){
dst[y]=prev;
}
prev=current;
}
dst++;
}
}

static void fons__maxCols(unsigned char* dst, int w, int h, int dstStride)
{
int x, y;
unsigned char prev, current;
for (y = 0; y < h; y++) {
prev=dst[0];
for (x = 1; x < w; x++) {
current=dst[x];
if(prev > current){
dst[x]=prev;
}
prev=current;
}
for (x = w-2; x >= 0; x--) {
current=dst[x];
if(prev > current){
dst[x]=prev;
}
prev=current;
}
dst += dstStride;
}
}

static void fons__maxDiagUp(unsigned char* dst, int w, int h, int dstStride)
{
int t, y;
const int a =dstStride-1;
const int d=w+h;
unsigned char prev, current;
for(t=0;t<d;t++){
const int y_min=(t-w<0)?0:t-w;
const int y_max=(t<h-1)?t:h-1;
prev=dst[t+y_min*a];
for(y=y_min;y<=y_max;y++){
current=dst[t+y*a];
if(prev > current){
dst[t+y*a]=prev;
}
prev=current;
}
for(y=y_max-2;y>=y_min;y--){
current=dst[t+y*a];
if(prev > current){
dst[t+y*a]=prev;
}
prev=current;
}
}
}

static void fons__maxDiagDown(unsigned char* dst, int w, int h, int dstStride)
{
int t, y;
const int a=(h-1)*dstStride;
const int b=dstStride+1;
const int d=w+h;
unsigned char prev, current;
for(t=0;t<d;t++){
const int y_min=(t-w<0)?0:t-w;
const int y_max=(t<h-1)?t:h-1;
prev=dst[t-y_min*b+a];
for(y=y_min;y<=y_max;y++){
current=dst[t-y*b+a];
if(prev > current){
dst[t-y*b+a]=prev;
}
prev=current;
}
for(y=y_max-2;y>=y_min;y--){
current=dst[t-y*b+a];
if(prev > current){
dst[t-y*b+a]=prev;
}
prev=current;
}
}
}

// Gray level morphological dilation approximated by convoling with a max stencil along
rgb2hsv marked this conversation as resolved.
Show resolved Hide resolved
// Diagonal convolution overlaps with horizontal & vertical, so we alternate between vertical & horizontal
// and diagonal directions to prevent the dilation from being too large.
static void fons__dilate(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int dilate)
{
(void)stash;

for(int iter=0;iter<dilate;iter++){
if(iter%2==0){
fons__maxRows(dst, w, h, dstStride);
fons__maxCols(dst, w, h, dstStride);
} else {
fons__maxDiagUp(dst,w,h,dstStride);
fons__maxDiagDown(dst,w, h,dstStride);
}
}
}

static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned int codepoint,
short isize, short iblur, int bitmapOption)
short isize, short iblur, short idilate, int bitmapOption)
{
int i, g, advance, lsb, x0, y0, x1, y1, gw, gh, gx, gy, x, y;
float scale;
Expand All @@ -1088,7 +1214,9 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in

if (isize < 2) return NULL;
if (iblur > 20) iblur = 20;
pad = iblur+2;
if (idilate > 20) idilate = 20;
const int antiAliasBonus = 2;
pad= antiAliasBonus + iblur + idilate;
rgb2hsv marked this conversation as resolved.
Show resolved Hide resolved

// Reset allocator.
stash->nscratch = 0;
Expand All @@ -1097,7 +1225,9 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in
h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1);
i = font->lut[h];
while (i != -1) {
if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) {
if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize
&& font->glyphs[i].blur == iblur
&& font->glyphs[i].dilate == idilate) {
glyph = &font->glyphs[i];
if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL || (glyph->x0 >= 0 && glyph->y0 >= 0)) {
return glyph;
Expand Down Expand Up @@ -1151,6 +1281,7 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in
glyph->codepoint = codepoint;
glyph->size = isize;
glyph->blur = iblur;
glyph->dilate = idilate;
glyph->next = 0;

// Insert char to hash lookup.
Expand Down Expand Up @@ -1195,6 +1326,13 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in
}
}*/

// Dilate
if (idilate > 0) {
stash->nscratch = 0;
bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width];
fons__dilate(stash, bdst, gw, gh, stash->params.width, idilate);
}

// Blur
if (iblur > 0) {
stash->nscratch = 0;
Expand Down Expand Up @@ -1331,6 +1469,7 @@ float fonsDrawText(FONScontext* stash,
int prevGlyphIndex = -1;
short isize = (short)(state->size*10.0f);
short iblur = (short)state->blur;
short idilate = (short)state->dilate;
float scale;
FONSfont* font;
float width;
Expand Down Expand Up @@ -1361,7 +1500,7 @@ float fonsDrawText(FONScontext* stash,
for (; str != end; ++str) {
if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str))
continue;
glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_REQUIRED);
glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, idilate, FONS_GLYPH_BITMAP_REQUIRED);
if (glyph != NULL) {
fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q);

Expand Down Expand Up @@ -1398,6 +1537,7 @@ int fonsTextIterInit(FONScontext* stash, FONStextIter* iter,

iter->isize = (short)(state->size*10.0f);
iter->iblur = (short)state->blur;
iter->idilate = (short)state->dilate;
iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f);

// Align horizontally
Expand Down Expand Up @@ -1445,7 +1585,7 @@ int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad)
// Get glyph and quad
iter->x = iter->nextx;
iter->y = iter->nexty;
glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur, iter->bitmapOption);
glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur, iter->idilate, iter->bitmapOption);
// If the iterator was initialized with FONS_GLYPH_BITMAP_OPTIONAL, then the UV coordinates of the quad will be invalid.
if (glyph != NULL)
fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad);
Expand Down Expand Up @@ -1518,6 +1658,7 @@ float fonsTextBounds(FONScontext* stash,
int prevGlyphIndex = -1;
short isize = (short)(state->size*10.0f);
short iblur = (short)state->blur;
short idilate = (short)state->dilate;
float scale;
FONSfont* font;
float startx, advance;
Expand All @@ -1543,7 +1684,7 @@ float fonsTextBounds(FONScontext* stash,
for (; str != end; ++str) {
if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str))
continue;
glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_OPTIONAL);
glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, idilate, FONS_GLYPH_BITMAP_OPTIONAL);
if (glyph != NULL) {
fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q);
if (q.x0 < minx) minx = q.x0;
Expand Down
13 changes: 13 additions & 0 deletions src/nanovg.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ struct NVGstate {
float letterSpacing;
float lineHeight;
float fontBlur;
float fontDilate;
int textAlign;
int fontId;
};
Expand Down Expand Up @@ -664,6 +665,7 @@ void nvgReset(NVGcontext* ctx)
state->letterSpacing = 0.0f;
state->lineHeight = 1.0f;
state->fontBlur = 0.0f;
state->fontDilate = 0.0f;
state->textAlign = NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE;
state->fontId = 0;
}
Expand Down Expand Up @@ -2353,6 +2355,12 @@ void nvgFontBlur(NVGcontext* ctx, float blur)
state->fontBlur = blur;
}

void nvgFontDilate(NVGcontext* ctx, float dilate)
{
NVGstate* state = nvg__getState(ctx);
state->fontDilate = dilate;
}

void nvgTextLetterSpacing(NVGcontext* ctx, float spacing)
{
NVGstate* state = nvg__getState(ctx);
Expand Down Expand Up @@ -2480,6 +2488,7 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char*
fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetDilate(ctx->fs, state->fontDilate*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);

Expand Down Expand Up @@ -2651,6 +2660,7 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa
fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetDilate(ctx->fs, state->fontDilate*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);

Expand Down Expand Up @@ -2835,6 +2845,7 @@ float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const
fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetDilate(ctx->fs, state->fontDilate*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);

Expand Down Expand Up @@ -2879,6 +2890,7 @@ void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, co
fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetDilate(ctx->fs, state->fontDilate*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);
fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy);
Expand Down Expand Up @@ -2930,6 +2942,7 @@ void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* l
fonsSetSize(ctx->fs, state->fontSize*scale);
fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
fonsSetBlur(ctx->fs, state->fontBlur*scale);
fonsSetDilate(ctx->fs, state->fontDilate*scale);
fonsSetAlign(ctx->fs, state->textAlign);
fonsSetFont(ctx->fs, state->fontId);

Expand Down
Loading