WIP: to use AGG font renderer to create a coverage bitmap
This commit is contained in:
parent
20626ab911
commit
0e713f7692
|
@ -0,0 +1,78 @@
|
||||||
|
```c
|
||||||
|
stbtt_InitFont
|
||||||
|
|
||||||
|
stbtt_ScaleForMappingEmToPixels x 3
|
||||||
|
stbtt_ScaleForPixelHeight
|
||||||
|
stbtt_BakeFontBitmap
|
||||||
|
stbtt_GetFontVMetrics x 2
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap
|
||||||
|
float xoff, yoff, xadvance;
|
||||||
|
} stbtt_bakedchar;
|
||||||
|
|
||||||
|
struct RenImage {
|
||||||
|
RenColor *pixels;
|
||||||
|
int width, height;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
RenImage *image;
|
||||||
|
stbtt_bakedchar glyphs[256];
|
||||||
|
} GlyphSet;
|
||||||
|
|
||||||
|
struct RenFont {
|
||||||
|
void *data;
|
||||||
|
stbtt_fontinfo stbfont;
|
||||||
|
GlyphSet *sets[MAX_GLYPHSET];
|
||||||
|
float size;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet).
|
||||||
|
Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB.
|
||||||
|
With a single call many glyphs corresponding to a range of codepoints, all in a
|
||||||
|
single image.
|
||||||
|
|
||||||
|
## STB truetype font metrics
|
||||||
|
|
||||||
|
stbtt_ScaleForPixelHeight takes a float 'height' and returns height / (ascent - descent).
|
||||||
|
|
||||||
|
stbtt_ScaleForMappingEmToPixels take a float 'pixels' and returns pixels / unitsPerEm.
|
||||||
|
|
||||||
|
### Computing RenFont
|
||||||
|
|
||||||
|
When loading a font, in renderer.c, the font->height is determined as:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int ascent, descent, linegap;
|
||||||
|
stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap);
|
||||||
|
float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size);
|
||||||
|
font->height = (ascent - descent + linegap) * scale + 0.5;
|
||||||
|
```
|
||||||
|
|
||||||
|
so, mathematically
|
||||||
|
|
||||||
|
```c
|
||||||
|
font->height = (ascent - descent + linegap) * font->size / unitsPerEm + 0.5;
|
||||||
|
```
|
||||||
|
|
||||||
|
**TO DO**: find out for what font->height is actually used.
|
||||||
|
|
||||||
|
### Call to BakeFontBitmap
|
||||||
|
|
||||||
|
In the same file, renderer.c, to create the glyphset image it computes:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Using stbtt functions
|
||||||
|
float s = ScaleForMappingEmToPixels(1) / ScaleForPixelHeight(1);
|
||||||
|
```
|
||||||
|
|
||||||
|
so 's' is actually equal to (ascent - descent) / unitsPerEm.
|
||||||
|
|
||||||
|
Then BakeFontBitmap is called and `font->size * s` is used for the pixel_height argument.
|
||||||
|
So BakeFontBitmap gets, for pixel_height, (ascent - descent) * font->size / unitsPerEm.
|
||||||
|
This is equal almost equal to font->height except the 0.5, the missing linegap calculation
|
||||||
|
and the fact that this latter is an integer instead of a float.
|
34
notes.txt
34
notes.txt
|
@ -1,34 +0,0 @@
|
||||||
stbtt_InitFont
|
|
||||||
|
|
||||||
stbtt_ScaleForMappingEmToPixels x 3
|
|
||||||
stbtt_ScaleForPixelHeight
|
|
||||||
stbtt_BakeFontBitmap
|
|
||||||
stbtt_GetFontVMetrics x 2
|
|
||||||
|
|
||||||
struct RenImage {
|
|
||||||
RenColor *pixels;
|
|
||||||
int width, height;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
RenImage *image;
|
|
||||||
stbtt_bakedchar glyphs[256];
|
|
||||||
} GlyphSet;
|
|
||||||
|
|
||||||
struct RenFont {
|
|
||||||
void *data;
|
|
||||||
stbtt_fontinfo stbfont;
|
|
||||||
GlyphSet *sets[MAX_GLYPHSET];
|
|
||||||
float size;
|
|
||||||
int height;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap
|
|
||||||
float xoff, yoff, xadvance;
|
|
||||||
} stbtt_bakedchar;
|
|
||||||
|
|
||||||
The function stbtt_BakeFontBitmap is used to write bitmap data into set->image->pixels (where set is a GlyphSet).
|
|
||||||
Note that set->image->pixels need data in RGB format. After stbtt_BakeFontBitmap call the bitmap data are converted into RGB.
|
|
||||||
With a single call many glyphs corresponding to a range of codepoints, all in a
|
|
||||||
single image.
|
|
|
@ -21,6 +21,7 @@ int FontRendererDrawText(FontRenderer *fr_, RenColor *pixels, int width, int hei
|
||||||
font_renderer_lcd *font_renderer = (font_renderer_lcd *) fr_;
|
font_renderer_lcd *font_renderer = (font_renderer_lcd *) fr_;
|
||||||
agg::rendering_buffer ren_buf((agg::int8u *) pixels, width, height, -pitch);
|
agg::rendering_buffer ren_buf((agg::int8u *) pixels, width, height, -pitch);
|
||||||
const agg::rgba8 agg_color(color.r, color.g, color.b);
|
const agg::rgba8 agg_color(color.r, color.g, color.b);
|
||||||
double new_x = font_renderer->render_text(ren_buf, (double) text_size, agg_color, (double) x, (double) (height - y), text);
|
double xd = x, yd = (height - y - text_size);
|
||||||
return int(new_x);
|
font_renderer->render_text(ren_buf, (double) text_size, agg_color, xd, yd, text);
|
||||||
|
return int(xd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,11 +63,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Rasterizer, class Scanline, class RenSolid>
|
template<class Rasterizer, class Scanline, class RenSolid>
|
||||||
double draw_text(Rasterizer& ras, Scanline& sl,
|
void draw_text(Rasterizer& ras, Scanline& sl,
|
||||||
RenSolid& ren_solid, const agg::rgba8 color,
|
RenSolid& ren_solid, const agg::rgba8 color,
|
||||||
const char* text,
|
const char* text,
|
||||||
double x, double y, double height,
|
double& x, double& y, double height,
|
||||||
unsigned subpixel_scale)
|
unsigned subpixel_scale)
|
||||||
{
|
{
|
||||||
const double scale_x = 100;
|
const double scale_x = 100;
|
||||||
|
|
||||||
|
@ -77,14 +77,14 @@ public:
|
||||||
|
|
||||||
const char* p = text;
|
const char* p = text;
|
||||||
|
|
||||||
x *= subpixel_scale;
|
double x_lcd = x * subpixel_scale;
|
||||||
double start_x = x;
|
double start_x_lcd = x_lcd;
|
||||||
|
|
||||||
while(*p)
|
while(*p)
|
||||||
{
|
{
|
||||||
if(*p == '\n')
|
if(*p == '\n')
|
||||||
{
|
{
|
||||||
x = start_x;
|
x_lcd = start_x_lcd;
|
||||||
y -= height * 1.25;
|
y -= height * 1.25;
|
||||||
++p;
|
++p;
|
||||||
continue;
|
continue;
|
||||||
|
@ -95,7 +95,7 @@ public:
|
||||||
{
|
{
|
||||||
if(m_kerning)
|
if(m_kerning)
|
||||||
{
|
{
|
||||||
m_fman.add_kerning(&x, &y);
|
m_fman.add_kerning(&x_lcd, &y);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_fman.init_embedded_adaptors(glyph, 0, 0);
|
m_fman.init_embedded_adaptors(glyph, 0, 0);
|
||||||
|
@ -105,19 +105,20 @@ public:
|
||||||
ras.reset();
|
ras.reset();
|
||||||
m_mtx.reset();
|
m_mtx.reset();
|
||||||
m_mtx *= agg::trans_affine_scaling(1.0 / scale_x, 1);
|
m_mtx *= agg::trans_affine_scaling(1.0 / scale_x, 1);
|
||||||
m_mtx *= agg::trans_affine_translation(start_x + x/scale_x, ty);
|
m_mtx *= agg::trans_affine_translation(start_x_lcd + x_lcd / scale_x, ty);
|
||||||
ras.add_path(m_trans);
|
ras.add_path(m_trans);
|
||||||
ren_solid.color(color);
|
ren_solid.color(color);
|
||||||
agg::render_scanlines(ras, sl, ren_solid);
|
agg::render_scanlines(ras, sl, ren_solid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// increment pen position
|
// increment pen position
|
||||||
x += glyph->advance_x;
|
x_lcd += glyph->advance_x;
|
||||||
y += glyph->advance_y;
|
y += glyph->advance_y;
|
||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
return x / (subpixel_scale * scale_x);
|
// Update x value befor returning.
|
||||||
|
x = x_lcd / subpixel_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear(agg::rendering_buffer& ren_buf, const agg::rgba8 color) {
|
void clear(agg::rendering_buffer& ren_buf, const agg::rgba8 color) {
|
||||||
|
@ -126,14 +127,14 @@ public:
|
||||||
ren_base.clear(color);
|
ren_base.clear(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
double render_text(agg::rendering_buffer& ren_buf,
|
void render_text(agg::rendering_buffer& ren_buf,
|
||||||
const double text_size,
|
const double text_size,
|
||||||
const agg::rgba8 text_color,
|
const agg::rgba8 text_color,
|
||||||
double x, double y,
|
double x, double y,
|
||||||
const char *text)
|
const char *text)
|
||||||
{
|
{
|
||||||
if (!m_font_loaded) {
|
if (!m_font_loaded) {
|
||||||
return y;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
agg::scanline_u8 sl;
|
agg::scanline_u8 sl;
|
||||||
|
@ -146,7 +147,7 @@ public:
|
||||||
agg::pixfmt_bgra32 pf(ren_buf);
|
agg::pixfmt_bgra32 pf(ren_buf);
|
||||||
base_ren_type ren_base(pf);
|
base_ren_type ren_base(pf);
|
||||||
renderer_solid ren_solid(ren_base);
|
renderer_solid ren_solid(ren_base);
|
||||||
y = draw_text(ras, sl, ren_solid, text_color, text, x, y, text_size, 1);
|
draw_text(ras, sl, ren_solid, text_color, text, x, y, text_size, 1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -158,8 +159,7 @@ public:
|
||||||
pixfmt_lcd_type pf_lcd(ren_buf, lut, m_gamma_lut);
|
pixfmt_lcd_type pf_lcd(ren_buf, lut, m_gamma_lut);
|
||||||
agg::renderer_base<pixfmt_lcd_type> ren_base_lcd(pf_lcd);
|
agg::renderer_base<pixfmt_lcd_type> ren_base_lcd(pf_lcd);
|
||||||
agg::renderer_scanline_aa_solid<agg::renderer_base<pixfmt_lcd_type> > ren_solid_lcd(ren_base_lcd);
|
agg::renderer_scanline_aa_solid<agg::renderer_base<pixfmt_lcd_type> > ren_solid_lcd(ren_base_lcd);
|
||||||
y = draw_text(ras, sl, ren_solid_lcd, text_color, text, x, y, text_size, 3);
|
draw_text(ras, sl, ren_solid_lcd, text_color, text, x, y, text_size, 3);
|
||||||
}
|
}
|
||||||
return y;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "SDL_surface.h"
|
||||||
|
#include "font_render_lcd.h"
|
||||||
|
|
||||||
|
#define MAX_GLYPHSET 256
|
||||||
|
|
||||||
|
typedef struct { uint8_t b, g, r, a; } RenColor;
|
||||||
|
typedef struct { int x, y, width, height; } RenRect;
|
||||||
|
|
||||||
|
struct RenImage {
|
||||||
|
RenColor *pixels;
|
||||||
|
int width, height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlyphSetA {
|
||||||
|
RenImage *image;
|
||||||
|
// FIXME: add glyphs information for AGG implementation
|
||||||
|
// stbtt_bakedchar glyphs[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenFontA {
|
||||||
|
font_renderer_lcd *renderer;
|
||||||
|
float size;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void* check_alloc(void *ptr) {
|
||||||
|
if (!ptr) {
|
||||||
|
fprintf(stderr, "Fatal error: memory allocation failed\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenImage* ren_new_image(int width, int height) {
|
||||||
|
assert(width > 0 && height > 0);
|
||||||
|
RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor));
|
||||||
|
check_alloc(image);
|
||||||
|
image->pixels = (void*) (image + 1);
|
||||||
|
image->width = width;
|
||||||
|
image->height = height;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ren_free_image(RenImage *image) {
|
||||||
|
free(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenFontA* ren_load_font_agg(const char *filename, float size) {
|
||||||
|
RenFont *font = NULL;
|
||||||
|
|
||||||
|
/* init font */
|
||||||
|
font = check_alloc(calloc(1, sizeof(RenFontA)));
|
||||||
|
font->size = size;
|
||||||
|
|
||||||
|
font->renderer = new font_renderer_lcd(true, false, true, 1.8);
|
||||||
|
font->renderer->load_font(filename);
|
||||||
|
|
||||||
|
// FIXME: figure out correct calculation for font->height with
|
||||||
|
// ascent, descent and linegap.
|
||||||
|
fit->height = size;
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GlyphSet* load_glyphset_agg(RenFont *font, int idx) {
|
||||||
|
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
|
||||||
|
|
||||||
|
/* init image */
|
||||||
|
int width = 128;
|
||||||
|
int height = 128;
|
||||||
|
retry:
|
||||||
|
set->image = ren_new_image(width, height);
|
||||||
|
|
||||||
|
/* load glyphs */
|
||||||
|
float s =
|
||||||
|
stbtt_ScaleForMappingEmToPixels(&font->stbfont, 1) /
|
||||||
|
stbtt_ScaleForPixelHeight(&font->stbfont, 1);
|
||||||
|
int res = stbtt_BakeFontBitmap(
|
||||||
|
font->data, 0, font->size * s, (void*) set->image->pixels,
|
||||||
|
width, height, idx * 256, 256, set->glyphs);
|
||||||
|
|
||||||
|
/* retry with a larger image buffer if the buffer wasn't large enough */
|
||||||
|
if (res < 0) {
|
||||||
|
width *= 2;
|
||||||
|
height *= 2;
|
||||||
|
ren_free_image(set->image);
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* adjust glyph yoffsets and xadvance */
|
||||||
|
int ascent, descent, linegap;
|
||||||
|
stbtt_GetFontVMetrics(&font->stbfont, &ascent, &descent, &linegap);
|
||||||
|
float scale = stbtt_ScaleForMappingEmToPixels(&font->stbfont, font->size);
|
||||||
|
int scaled_ascent = ascent * scale + 0.5;
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
set->glyphs[i].yoff += scaled_ascent;
|
||||||
|
set->glyphs[i].xadvance = floor(set->glyphs[i].xadvance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* convert 8bit data to 32bit */
|
||||||
|
for (int i = width * height - 1; i >= 0; i--) {
|
||||||
|
uint8_t n = *((uint8_t*) set->image->pixels + i);
|
||||||
|
set->image->pixels[i] = (RenColor) { .r = 255, .g = 255, .b = 255, .a = n };
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr, "usage: %s <font-filename> <size>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const char *filename = argv[1];
|
||||||
|
const int size = atoi(argv[2]);
|
||||||
|
RenFont *font = ren_load_font(filename, size);
|
||||||
|
GlyphSet *set = load_glyphset(font, 0);
|
||||||
|
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom(
|
||||||
|
set->image->pixels,
|
||||||
|
set->image->width, set->image->height, 32, set->image->width * 4,
|
||||||
|
SDL_PIXELFORMAT_RGBA32);
|
||||||
|
SDL_SaveBMP(surface, "stb-glyphset.bmp");
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue