#### Archived

This topic is now archived and is closed to further replies.

# Creating outlined text!

This topic is 5872 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

Hey ... I''ve been looking around the net like crazy for info on how to do outlined text. And with that, I mean using the DirectX text routines or what not, to make ... e.g white text with black outline around it. Commonly seen on PSX/PS2 / N64 games ... Heeelp!

##### Share on other sites
bump..

Jason Arora
jason@pubism.com
http://www.pubism.com

##### Share on other sites
you could use bitmapped fonts... that is, draw your outlined characters in a bitmap, and then blt the individual letters to the screen... this doesn''t allow for pretty scaling, but if you only need one or two sized fonts it''d be ok.

--- krez (krezisback@aol.com)

##### Share on other sites
I was thinking about that also, but wouldn''t that be slow ?

Jason Arora
jason@pubism.com
http://www.pubism.com

##### Share on other sites
I think so too ... But surely there must be a way of doing this using the normal text output functions ...

##### Share on other sites
CD3DFont in the SDK - you can easily do something similar with either your own bitmap or two passes with 2 different fonts.

##### Share on other sites
Blitting a bitmap font would not be slow. That method is what you see when you see the outlined text on N64 or PS2 games. Another method is like the other guy said, to blit out the text in black, and then blit out the text in white on top of it, but a little smaller so the black will show around it.

dave

--
david@neonstar.net
neonstar entertainment

##### Share on other sites
Its not exactly outlines, but you can get a good shadow effect that makes text more readable by blitting black text at x,y, then white text at x-1,y-1

##### Share on other sites
mhm ... ''kay.

Now for my second question. I''ve never worked with bitmapped fonts ;p .. anyone able to help me out there? Each char would have a fixed width and height on the map, right? So you read that certain area, and mask_blit it, right? But how is the lookup done (Efficiently)

##### Share on other sites
just draw the bitmapped font in ASCII order, so the ASCII value of each character is also the index of that character in the bitmap.
as far as fixed-widthness goes, that''s how i have done it... but i suppose you could make them variable-widthed. you''d have to keep track of the whole rect for each character in a lookup table then, though, instead of just multiplying the offset by the index.

--- krez (krezisback@aol.com)

##### Share on other sites
One more time...

CD3DFont in the SDK...

##### Share on other sites
Bitmapped fonts would provide you with a solution but they do not scale very well on different resolutions. Also, creating lots of different typefaces would be a nightmare.

There is a method which would mean a lot more programming but would a) work with any resolution and indeed any size for your font and b) work with any style of font.

By using the Win32 function GetGlyphOutline you can actually get the meta information for drawing characters from a chosen font. For each character it will return a list of drawing primitives, line and curves that make up the shape of the character.

This is a mechanism I used when I first looked at the Macromedia Flash SDK, because it had no functionality for displaying text. Like I said it is more work but it does work.

It will also allow you to change colours easily, and you can then fill the text with a bitmap brush, solid colour, whatever you want.

If you want an example I can post it here, although it''s specifically for the Flash SDK but you could munge it for whatever purpose.

##### Share on other sites
Sure, I''d like an example =)

##### Share on other sites
I started putting the example code in but it was just turning into a nightmare because of the other SDK so heavily integrated. Plus the SDK used quadratic splines which made it easy, PolyBezier (the function we would use to draw on a device context) uses bezier curves.

What I''ll do is modify the code so that it works, then post it as a self contained function (hopefully).

In fact, in the meantime I have found an MSDN article that does half the work. Take a look at article #Q243285 - this will convert splines to beziers. This takes the output from the GetGlyphOutline function.

I''ll post the working code, once it works.

##### Share on other sites
Goes a little something like this - after a bit of editing

  void OutlineText(HDC hdc, const char *font, const char *text, int PointSize, int xStart, int yStart){ HDC hdcScreen; HFONT hFont; HGDIOBJ hOldFont; int nHeight; TTPOLYGONHEADER *header; TTPOLYGONHEADER *lastheader = NULL; int nWidths[256]; // create a device context hdcScreen = CreateCompatibleDC(hdc); // set the mapping mode so that we can calculate the font height accurately SetMapMode(hdcScreen, MM_TEXT); nHeight = -MulDiv(PointSize, GetDeviceCaps(hdcScreen, LOGPIXELSY), 72); // create a font with the requested height hFont = CreateFont(nHeight, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, font); // and select it into the device context hOldFont = SelectObject(hdcScreen, hFont); // now get the widths of the glyphs so that we can update the coordinates for the shapes when we add each letter GetCharWidth32(hdcScreen, 0, 255, nWidths); // move the starting point to the requested location MoveToEx(hdc, xStart, yStart, NULL); // process each character for (const char *ch = text; *ch != 0; ch++) { // Don't move the advance for the first character, but for every one after that. if (ch - text > 0) { xStart += nWidths[*(ch - 1)]; } int c2 = *ch; GLYPHMETRICS gm; DWORD size; MAT2 matrix; FIXED f1; f1.value = 1; f1.fract = 0; FIXED f0; f0.value = 0; f0.fract = 0; matrix.eM11 = f1; matrix.eM12 = f0; matrix.eM21 = f0; matrix.eM22 = f1; char *buffer; if (c2 != ' ') { // get the size of the buffer needed for this character if ((size = GetGlyphOutline(hdcScreen, c2, GGO_NATIVE, &gm, 0, NULL, &matrix)) != GDI_ERROR) { buffer = new char[size+1]; } // get the glyph for this character if ((size = GetGlyphOutline(hdcScreen, c2, GGO_NATIVE, &gm, size, buffer, &matrix)) != GDI_ERROR) { DrawT2Outline(hdc, (TTPOLYGONHEADER *)buffer, size, xStart, yStart); delete[] buffer; } } // if (c2 != ' ') } // clean up SelectObject(hdcScreen, hOldFont); DeleteObject(hFont); DeleteObject(hdcScreen); return;}/**************************************************************************** * FUNCTION : IntFromFixed * RETURNS : int value approximating the FIXED value. ****************************************************************************/ int PASCAL NEAR IntFromFixed(FIXED f){ if (f.fract >= 0x8000) return(f.value + 1); else return(f.value);}/**************************************************************************** * FUNCTION : fxDiv2 * RETURNS : (val1 + val2)/2 for FIXED values ****************************************************************************/ FIXED PASCAL NEAR fxDiv2(FIXED fxVal1, FIXED fxVal2){ long l; l = (*((long far *)&(fxVal1)) + *((long far *)&(fxVal2)))/2; return(*(FIXED *)&l);}/**************************************************************************** * FUNCTION : MakeBezierFromLine * * PURPOSE : Converts a line define by two points to a four point Bezier * spline representation of the line in pPts. * * * RETURNS : number of Bezier points placed into the pPts POINT array. ****************************************************************************/ UINT MakeBezierFromLine( POINT *pPts, POINT startpt, POINT endpt ){ UINT cTotal = 0; // starting point of Bezier pPts[cTotal] = startpt; cTotal++; // 1rst Control, pt == endpoint makes Bezier a line pPts[cTotal].x = endpt.x; pPts[cTotal].y = endpt.y; cTotal++; // 2nd Control, pt == startpoint makes Bezier a line pPts[cTotal].x = startpt.x; pPts[cTotal].y = startpt.y; cTotal++; // ending point of Bezier pPts[cTotal] = endpt; cTotal++; return cTotal;}/**************************************************************************** * FUNCTION : MakeBezierFromQBSpline * * PURPOSE : Converts a quadratic spline in pSline to a four point Bezier * spline in pPts. * * * RETURNS : number of Bezier points placed into the pPts POINT array. ****************************************************************************/ UINT MakeBezierFromQBSpline( POINT *pPts, POINTFX *pSpline ){ POINT P0, // Quadratic on curve start point P1, // Quadratic control point P2; // Quadratic on curve end point UINT cTotal = 0; // Convert the Quadratic points to integer P0.x = IntFromFixed( pSpline[0].x ); P0.y = IntFromFixed( pSpline[0].y ); P1.x = IntFromFixed( pSpline[1].x ); P1.y = IntFromFixed( pSpline[1].y ); P2.x = IntFromFixed( pSpline[2].x ); P2.y = IntFromFixed( pSpline[2].y ); // conversion of a quadratic to a cubic // Cubic P0 is the on curve start point pPts[cTotal] = P0; cTotal++; // Cubic P1 in terms of Quadratic P0 and P1 pPts[cTotal].x = P0.x + 2*(P1.x - P0.x)/3; pPts[cTotal].y = P0.y + 2*(P1.y - P0.y)/3; cTotal++; // Cubic P2 in terms of Qudartic P1 and P2 pPts[cTotal].x = P1.x + 1*(P2.x - P1.x)/3; pPts[cTotal].y = P1.y + 1*(P2.y - P1.y)/3; cTotal++; // Cubic P3 is the on curve end point pPts[cTotal] = P2; cTotal++; return cTotal;}/**************************************************************************** * FUNCTION : AppendPolyLineToBezier * * PURPOSE : Converts line segments into their Bezier point * representation and appends them to a list of Bezier points. * * WARNING - The array must have at least one valid * start point prior to the address of the element passed. * * RETURNS : number of Bezier points added to the POINT array. ****************************************************************************/ UINT AppendPolyLineToBezier( LPPOINT pt, POINTFX start, LPTTPOLYCURVE lpCurve ){ int i; UINT cTotal = 0; POINT endpt; POINT startpt; POINT bezier[4]; endpt.x = IntFromFixed(start.x); endpt.y = IntFromFixed(start.y); for (i = 0; i < lpCurve->cpfx; i++) { // define the line segment startpt = endpt; endpt.x = IntFromFixed(lpCurve->apfx[i].x); endpt.y = IntFromFixed(lpCurve->apfx[i].y); // convert a line to a bezier representation MakeBezierFromLine( bezier, startpt, endpt ); // append the Bezier to the existing ones // Point 0 is Point 3 of previous. pt[cTotal++] = bezier[1]; // Point 1 pt[cTotal++] = bezier[2]; // Point 2 pt[cTotal++] = bezier[3]; // Point 3 } return cTotal;}/**************************************************************************** * FUNCTION : AppendQuadBSplineToBezier * * PURPOSE : Converts Quadratic spline segments into their Bezier point * representation and appends them to a list of Bezier points. * * WARNING - The array must have at least one valid * start point prior to the address of the element passed. * * RETURNS : number of Bezier points added to the POINT array. ****************************************************************************/ UINT AppendQuadBSplineToBezier( LPPOINT pt, POINTFX start, LPTTPOLYCURVE lpCurve ){ WORD i; UINT cTotal = 0; POINTFX spline[3]; // a Quadratic is defined by 3 points POINT bezier[4]; // a Cubic by 4 // The initial A point is on the curve. spline[0] = start; for (i = 0; i < lpCurve->cpfx;) { // The B point. spline[1] = lpCurve->apfx[i++]; // Calculate the C point. if (i == (lpCurve->cpfx - 1)) { // The last C point is described explicitly // i.e. it is on the curve. spline[2] = lpCurve->apfx[i++]; } else { // C is midpoint between B and next B point // because that is the on curve point of // a Quadratic B-Spline. spline[2].x = fxDiv2( lpCurve->apfx[i-1].x, lpCurve->apfx[i].x ); spline[2].y = fxDiv2( lpCurve->apfx[i-1].y, lpCurve->apfx[i].y ); } // convert the Q Spline to a Bezier MakeBezierFromQBSpline( bezier, spline ); // append the Bezier to the existing ones // Point 0 is Point 3 of previous. pt[cTotal++] = bezier[1]; // Point 1 pt[cTotal++] = bezier[2]; // Point 2 pt[cTotal++] = bezier[3]; // Point 3 // New A point for next slice of spline is the // on curve C point of this B-Spline spline[0] = spline[2]; } return cTotal;}/**************************************************************************** * FUNCTION : CloseContour * * PURPOSE : Adds a bezier line to close the circuit defined in pt. * * * RETURNS : number of points aded to the pt POINT array. ****************************************************************************/ UINT CloseContour( LPPOINT pt, UINT cTotal ){ POINT endpt, startpt; // definition of a line POINT bezier[4]; // connect the first and last points by a line segment startpt = pt[cTotal-1]; endpt = pt[0]; // convert a line to a bezier representation MakeBezierFromLine( bezier, startpt, endpt ); // append the Bezier to the existing ones // Point 0 is Point 3 of previous. pt[cTotal++] = bezier[1]; // Point 1 pt[cTotal++] = bezier[2]; // Point 2 pt[cTotal++] = bezier[3]; // Point 3 return 3;}/**************************************************************************** * FUNCTION : DrawT2Outline * * PURPOSE : Decode the GGO_NATIVE outline, create a sequence of Beziers * for each contour, draw with PolyBezier. Color and relative * positioning provided by caller. The coordinates of hDC are * assumed to have MM_TEXT orientation. * * The outline data is not scaled. To draw a glyph unhinted * the caller should create the font at its EMSquare size * and retrieve the outline data. Then setup a mapping mode * prior to calling this function. * * RETURNS : none. ****************************************************************************/ void DrawT2Outline(HDC hDC, LPTTPOLYGONHEADER lpHeader, DWORD size, int x, int y) { WORD i; UINT cTotal = 0; // Total points in a contour. LPTTPOLYGONHEADER lpStart; // the start of the buffer LPTTPOLYCURVE lpCurve; // the current curve of a contour LPPOINT pt; // the bezier buffer POINTFX ptStart; // The starting point of a curve DWORD dwMaxPts = size/sizeof(POINTFX); // max possible pts. DWORD dwBuffSize; dwBuffSize = dwMaxPts * // Maximum possible # of contour points. sizeof(POINT) * // sizeof buffer element 3; // Worst case multiplier of one additional point // of line expanding to three points of a bezier lpStart = lpHeader; pt = (LPPOINT)malloc( dwBuffSize ); // Loop until we have processed the entire buffer of contours. // The buffer may contain one or more contours that begin with // a TTPOLYGONHEADER. We have them all when we the end of the buffer. while ((DWORD)lpHeader < (DWORD)(((LPSTR)lpStart) + size) && pt != NULL) { if (lpHeader->dwType == TT_POLYGON_TYPE) // Draw each coutour, currently this is the only valid // type of contour. { // Convert the starting point. It is an on curve point. // All other points are continuous from the "last" // point of the contour. Thus the start point the next // bezier is always pt[cTotal-1] - the last point of the // previous bezier. See PolyBezier. cTotal = 1; pt[0].x = IntFromFixed(lpHeader->pfxStart.x); pt[0].y = IntFromFixed(lpHeader->pfxStart.y); // Get to first curve of contour - // it starts at the next byte beyond header lpCurve = (LPTTPOLYCURVE) (lpHeader + 1); // Walk this contour and process each curve( or line ) segment // and add it to the Beziers while ((DWORD)lpCurve < (DWORD)(((LPSTR)lpHeader) + lpHeader->cb)) { //********************************************** // Format assumption: // The bytes immediately preceding a POLYCURVE // structure contain a valid POINTFX. // // If this is first curve, this points to the // pfxStart of the POLYGONHEADER. // Otherwise, this points to the last point of // the previous POLYCURVE. // // In either case, this is representative of the // previous curve's last point. //********************************************** ptStart = *(LPPOINTFX)((LPSTR)lpCurve - sizeof(POINTFX)); if (lpCurve->wType == TT_PRIM_LINE) { // convert the line segments to Bezier segments cTotal += AppendPolyLineToBezier( &pt[cTotal], ptStart, lpCurve ); i = lpCurve->cpfx; } else if (lpCurve->wType == TT_PRIM_QSPLINE) { // Decode each Quadratic B-Spline segment, convert to bezier, // and append to the Bezier segments cTotal += AppendQuadBSplineToBezier( &pt[cTotal], ptStart, lpCurve ); i = lpCurve->cpfx; } else // Oops! A POLYCURVE format we don't understand. ; // error, error, error // Move on to next curve in the contour. lpCurve = (LPTTPOLYCURVE)&(lpCurve->apfx[i]); } // Add points to close the contour. // All contours are implied closed by TrueType definition. // Depending on the specific font and glyph being used, these // may not always be needed. if ( pt[cTotal-1].x != pt[0].x || pt[cTotal-1].y != pt[0].y ) { cTotal += CloseContour( pt, cTotal ); } // flip coordinates to get glyph right side up (Windows coordinates) // TT native coordiantes are zero originate at lower-left. // Windows MM_TEXT are zero originate at upper-left. for (i = 0; i < cTotal; i++) { pt[i].y = 0 - pt[i].y; pt[i].x += x; pt[i].y += y; } // Draw the contour PolyBezier( hDC, pt, cTotal ); } else // Bad, bail, must have a bogus buffer. break; // error, error, error // Move on to next Contour. // Its header starts immediate after this contour lpHeader = (LPTTPOLYGONHEADER)(((LPSTR)lpHeader) + lpHeader->cb); } free( pt );}

Note: I have included the MSDN code too because I have modified bits of it. Hope this is OK Microsoft?

This is by no means perfect so please, no flames. I may even post a cleaner solution another time. But now it's past my bedtime so I'll bid you adieu!

Edited by - JY on November 13, 2001 7:20:41 PM

Edited by - JY on November 13, 2001 7:24:22 PM

Edited by - JY on November 13, 2001 7:25:02 PM

Edited by - JY on November 13, 2001 7:26:30 PM

• ### Forum Statistics

• Total Topics
628647
• Total Posts
2984032

• 10
• 9
• 9
• 10
• 21