Help with bitmap font rendering

Started by
4 comments, last by ValMan 13 years, 5 months ago
Hello,

I implemented custom font rendering in my engine using bitmapped fonts (loaded at runtime from true type or raster font). Most of it is working well, however I am getting visual artifacts concerning the horizontal and vertical spacing of drawn glyphs from the baseline and each other.

It was very challenging for me to figure out how to apply correct spacing, so perhaps I just haven't figured it out all the way. The biggest problem is spacing of some letters from the baseline on the second label on the form, the one with Courier New true type font applied. Some of the glyphs are "riding low" as you can see. The first label also has weird spacing between "e" and "s".



Please take a look at pseudo code below, am I doing everything right?

1. Add pre-draw spacing to draw position before drawing glyph. This is the abcA value from Win32 ABC structure.

DrawPosition.x += Spacing.A


2. Draw glyph using following coordinates.

X = DrawPosition.x + KerningPairs[PrevChar][ThisChar].KernAmount;Y = DrawPosition.y + TextMetrics.Ascent - GlyphMetrics.Origin.y;


3. Add post-draw spacing to draw position after drawing glyph. This is the abcB and abcC values from ABC struct.

DrawPosition.x += Spacing.B + Spacing.C


[Edited by - ValMan on October 12, 2010 5:35:46 PM]
Advertisement
I use freetype myself so may be missing something obvious.

But I'd look to clamp some of those values to integer. If any of those returns are in ints then the final result probably needs clamping after you've added on some float values.

These kinda shit usually comes down to pixel centres. There's something about that in the docs or you can google it.
------------------------------Great Little War Game
I've recently had to do a font renderer for work stuff.

Basically, each character has a glyph origin and size. To find how far to move to the next character, you need to step forwards by the cell increment. I never got it to work using anything other than GetGlyphOutline. That gives you a GLYPHMETRICS struct, which contains all the information you need.

Here's some sample code (untested and error checking omitted), which draws a character to a HDC (I.e. your bitmap), and gives you the offsets nessecary to draw the character properly:
HDC hDC;  // Device context with the font selected into itchar ch;  // Character we're interested in// Get the text metrics to get the worst case character sizeTEXTMETRIC tm;GetTextMetrics(hDC, &tm);int nCellWidth = tm.tmMaxCharWidth;int nCellHeight = tm.tmHeight;// Get the glyph metrics for this characterMAT2 mat;memset(&mat, 0, sizeof(mat));mat.eM11.value = 1;mat.eM22.value = 1;GLYPHMETRICS glyphMetrics;GetGlyphOutlineW(hDC, ch, GGO_METRICS, &glyphMetrics, 0, NULL, &mat);// Determine where to draw character (Aim for (0, 0))RECT rc;rc.left = 0;rc.right = nCellWidth;rc.top = 0;rc.bottom = nCellHeight;// Offset character by it's origin, so (0, 0) contains the top left pixelrc.left -= glyphMetrics.gmptGlyphOrigin.x;rc.top -= tm.tmAscent - glyphMetrics.gmptGlyphOrigin.y;UINT nCharW = glyphMetrics.gmBlackBoxX;UINT nCharH = glyphMetrics.gmBlackBoxY;// Draw the character to the HDCDrawText(hDC, &ch, 1, &rc, DT_LEFT | DT_NOCLIP | DT_NOPREFIX);

When rendering this character, the bitmap contains the character in the region:
top = 0, left = 0, width = nCharW, height = nCharH
The character should be drawn at:
x = draw_x + glyphMetrics.gmptGlyphOrigin.x, y = draw_y + tm.tmAscent - glyphMetrics.gmptGlyphOrigin.y
Where draw_x and draw_y are where you want to draw the character normally
After drawing the character, the cursor should move right from the last draw_x by glyphMetrics.gmCellIncX.
Thank you for your replies.

I fixed the horizontal inter-letter spacing, that works fine now.

The spacing of characters from the baseline is still incorrect. I am using exactly the same formula as what you posted. With font Courier New point size 11, True Type, glyphs packed into texture with their black boxes as bounding boxes, characters like "e", "s" and "o" fall a few pixels below the baseline. I am using the Ascent - Origin formula to calculate Y position of the glyph related to current drawing position, exactly the way you do.

The origin of those problematic letters is 6 pixels down from top of glyph image's black box, same as the origin of some non-problematic letters such as "n". I am guessing there is some magical value that I'm supposed to be adding/subtracting that will be meaningful for "e", "s" and "o" but set to zero for most other letters. I have no idea what that value could be, maybe something as part of TEXTMETRIC structure?
Quote:Original post by ValMan
The origin of those problematic letters is 6 pixels down from top of glyph image's black box, same as the origin of some non-problematic letters such as "n". I am guessing there is some magical value that I'm supposed to be adding/subtracting that will be meaningful for "e", "s" and "o" but set to zero for most other letters. I have no idea what that value could be, maybe something as part of TEXTMETRIC structure?
Yup - for n, you might get something like this:

..................................................,######.....#.....#.....#.....#.....#.....#.....#.....#...............

With the glyph origin set to (2, 4) and the black box size set to 7x5 (I think, the ascent comes into it somehow but I can't think how; my brain is fried just now).
The code I pasted above will draw the character with the top left pixel (the comma above) at (0, 0), so you need to offset it by the glyph origin.
For anyone interested, the problem was resolved. Turns out GetGlyphOutline returns different values when it's called with GGO_METRICS versus GGO_BITMAP flags. GGO_METRICS is used to get only metrics and GGO_BITMAP is used to retrieve the bitmap image of the glyph, which also returns metrics. I was using part of spacing info from metrics returned with GGO_METRICS, and part with metrics returned with GGO_BITMAP which resulted in incorrect "origin" value being set for the glyph. This caused certain glyphs to appear 1-2 pixels below the baseline.

I modified my code to call GetGlyphOutline funtion with GGO_METRICS flag only for estimating how much space the next character to be copied into texture atlas will take. GetGlyphOutline with GGO_BITMAP I call when I am ready to retrieve the glyph image, and I am using the final spacing from that to set the glyph origin.

The problem with kerning was also resolved, some stupid logic error in my code. GetKernPairs works fine and returns reliable information.

This topic is closed to new replies.

Advertisement