Skip to main content
gamedev.net
🔒 Locked

DirectX DrawText method with outline

Started by BrakePad 8 replies 15.3k views
Original Post
BrakePad
BrakePad
Hi all, I've been developing a pretty simple project that overlays text ontop of solid colour/static image/video/live video feed for the purposes of presentation... I've got everything working so far pretty nicely but I'm looking to give the user more flexibility on how the text looks - they can add shadows, change the colour etc but I can't for the life of me figure out how to add a border to the text. I'm using the DirectX Font class to render the text ontop of a bog standard textured quad using the DrawText method. Couple anybody point me into the right direction of how to render a solid border (outline) around the text and change its colour? Any help would be much appreciated bp
Evil Steve
Evil Steve
ID3DXFont is just a helper class, it's not fully functional. To do anything fancy, you'll have to write your own text renderer.

However, for a simple outline, you could render the text slightly larger, in a different colour, that's give you a fake outline look. You'll still have to do the text spacing manually, but that's relatively easy. Look into GetGlyphOutline to get information about a character.
BrakePad
BrakePad
cheers for the quik reply!

I've already gone down the "drawing a slightly larger font behind" route, but I want the user to be able to use whatever font they want, so line spacing changes become impossible!

Ah well, I feared I may have to work on my own class - I've already made a DirectShow video class as the M$ version had a serious memory leak when rendering to texture!

Thanks anyway Steve

EDIT:

I notice the GetGlyphOutline is GDI - I've been told that mixing GDI and DirectX is not a good idea! Will it be alright?
Evil Steve
Evil Steve
Quote:
Original post by BrakePad
I've already gone down the "drawing a slightly larger font behind" route, but I want the user to be able to use whatever font they want, so line spacing changes become impossible!
Line spacing shouldn't matter if you're rendering each letter separately.
BrakePad
BrakePad
Heya...

I've been looking at various ways to achieve this, but need a little more direction:

1. Using GDI to render the text onto the render target - this is very slow (slows my app from 350fps to 50fps) and gets slower with larger fragments of text. I would rather avoid this option.

2. Writing a class (using DirectX) that is similar to the DrawText method, but incorporates borders...

I would rather follow the latter of the two options, but have no idea where to start! Could somebody give me some direction as to how I go about printing letters seperately and stroking them with a border...

Any more help much appreciated

bp
Evil Steve
Evil Steve
Quote:
Original post by BrakePad
Heya...

I've been looking at various ways to achieve this, but need a little more direction:

1. Using GDI to render the text onto the render target - this is very slow (slows my app from 350fps to 50fps) and gets slower with larger fragments of text. I would rather avoid this option.

2. Writing a class (using DirectX) that is similar to the DrawText method, but incorporates borders...

I would rather follow the latter of the two options, but have no idea where to start! Could somebody give me some direction as to how I go about printing letters seperately and stroking them with a border...

Any more help much appreciated

bp
1. Never write directly to the render target. What I do (And what I suspect D3DXFont does), is create a DIB section and write to that. Then you can get access to the DIB bits, Lock() a texture, and copy bits into it. So you're just updating a dynamic texture whenever you add glyphs, not touching the render target. When you come to render, you render a list of triangles, using your dynamic texture with texture coordinates set so that you draw the glyphs. I had a blurb about it in my journal a while ago, but I can't seem to find it...

2. You'd need to call ID3DXFont::DrawText() twice for each letter (If you pass an ID3DXSprite pointer, this shouldn't be very inefficient at all), once to draw the outline, and once to draw the main letter. The positioning of the letters is up to you though, you'll have to get each letter's glyph outline to determine it's position. From my text renderer:
// Get font ascentTEXTMETRIC tm;if(!GetTextMetrics(m_hDC, &tm)){	ELog::Get().DebugFormat("FONT     : GetTextMetrics() failed. Error: %s\n",		PApp::FormatWindowsError(GetLastError()).c_str());	return false;}// Get the size of the glyph blackboxGLYPHMETRICS glyphMetrics;MAT2 mat;memset(&mat, 0, sizeof(mat));mat.eM11.value = 1;mat.eM22.value = 1;if(GetGlyphOutline(m_hDC, ch, GGO_METRICS, &glyphMetrics, 0, NULL, &mat) == GDI_ERROR){	// This is probably not a truetype font - approximate sizes	SIZE sizeGlyph;	if(!GetTextExtentPoint32W(m_hDC, &ch, 1, &sizeGlyph))	{		bRet = false;		break;	}	memset(&glyphMetrics, 0, sizeof(glyphMetrics));	glyphMetrics.gmBlackBoxX = sizeGlyph.cx;	glyphMetrics.gmBlackBoxY = sizeGlyph.cy;	glyphMetrics.gmptGlyphOrigin.x = 0;	glyphMetrics.gmptGlyphOrigin.y = tm.tmAscent;	glyphMetrics.gmCellIncX = (short)sizeGlyph.cx;}// Snip...// Update m_theGlyphsm_theGlyphs[ch].rcGlyph.x = (u8)0;m_theGlyphs[ch].rcGlyph.y = (u8)0;m_theGlyphs[ch].rcGlyph.w = (u8)glyphMetrics.gmBlackBoxX;m_theGlyphs[ch].rcGlyph.h = (u8)glyphMetrics.gmBlackBoxY;m_theGlyphs[ch].nXOrigin = glyphMetrics.gmptGlyphOrigin.x;m_theGlyphs[ch].nYOrigin = tm.tmAscent - glyphMetrics.gmptGlyphOrigin.y;m_theGlyphs[ch].nWidth = glyphMetrics.gmCellIncX;

Where ch is the character to check, and m_hDC is the device context the font is in (Which you can get from ID3DXFont::GetDC()).

That fills in the information you see at the bottom of that snippet. You can use the nXOrigin, nYOrigin, and nWidth variables there to determine where the glyph is to be drawn.

Even so, it's pretty complicated; I'd recommend doing something more simple, such as just using a bitmap font. I can post my entire font rendering code here if it helps, but some of it is abstracted away into my own wrapper classes, so it'll require some shuffling to get working.
Endurion
Endurion
Simple, but brutal method:

Can't you just render the text 5 times? 4 times with a little offset (+ 1 pixel) in one direction with black, and then the correct color in the center.

This is easy to implement, but it's of course quite a bit of overdraw.
Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>
warthog518
warthog518
i used fireworks and used 200 pt lucida console and created bitmaps for every single letter(upper/lower) and number + symbols.... then i loaded all the bitmaps (around 50->100?) with directx and wrote my own Text class... its quite time consuming.... i reccomend you use fix-sized fonts
even with my text class, its hard to do such a thing, i have to draw a duplicate of each letter under the previous letter...
a sentance like

Hello I am Warthog518
has 72 triangles in it, thats 288 verticies... it also takes quite a while to load all the textures as well

Oh, and if you really plan on having a text effect like that, it looks way better if u draw the border with opacity 20 with decreasing sizes (Starting from maybe 20 pixels larger and decreasing size?) it gives a "Shadow" effect :P

but then again, that makes it like 500 triangles.... which is not really effective

you could also use teh "DrawText" feature which doesnt really support depth

Microsoft has a screensaver that shows 3d text bouncing around... you can also select what font they use and it supports depth, how do they "extrude" a font? o_o
BrakePad
BrakePad
Thanks for everyone's input, I think I've made a way forward on this one...

For the information of anybody reading this thread later on, the solution I've used, and am now happy with is as follows:

1. To achieve the stroked text (border) effect, I render the border using the follwoing code:

for (int i = 0; i <= intBorderWidth; i += 1){    for (int theta = 0; theta <= 360; theta += angleStep)    {        double cos, sin;        cos = i * Math.Cos((double)((theta) * (Math.PI / 180)));        sin = i * Math.Sin((double)((theta) * (Math.PI / 180)));        D3DFontBorder.DrawText(null, text, new Rectangle(rect.Left - (int)sin, rect.Top - (int)cos, rect.Width, rect.Height), dtxtFormat, strokeColour);    }}


This draws the text at offsets from 1 to intBorderWidth in a circle at angle intervals of angleStep. This gives a perfect stroke of the text.

Obviously, this is far too much processing to be rendering 3-400 times a second, so I run this code once at startup to render it to a texture, which is then rendered to the screen.

I went with this implementation (idea courtesy of Endurion) as it will work with ANY font - so the user has complete control over the font they require, and can implement any size of Border. Also, it is much easier (for me anyway!) that writing a whole text blitting class. I have also expanded the idea to encorporate Photoshop style 'Drop Shaodws' by altering the opacity of each stroke to decrease as intBorderWidth increases.

Sample

This gives you an idea of what I've achieved (the background is a video). The Drop Shadow is there, it's just quite subtle - bad choice of screenshot on my part

Thanks for everyone's help,

bp

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.