Outline Text, Part 1

Introduction

I am an avid fan of animes(Japanese animations). As I do not understand the Japanese language, the animes which I watched, have English subtitles. These fan-subbed animes have the most beautiful fonts and text. Below is a screenshot of the "Tales of the Abyss", an anime based on a fantasy game with the same name.

I was fascinated by the outline text. I searched on the web for an outline text library which allows me to do outline text. Sadly I found none. Those that I found, are too difficult for me to retrofit them to my general purpose and I do not fully understand their sparsely commented codes. I decided to roll up my sleeves to write my own outline text library.

Single Outline

Above is an example outline text. Below is the generic GDI+ code to display such a text, using GraphicsPath class. Generally, to draw outline text, you have to get the text path, then render the outline with the text path and render the text by filling the interior of the path.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

Font font(pDC->GetSafeHdc(), &lf);
GraphicsPath path(FillModeWinding);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t buf[] = L"CodeGuru Is The Best!";

// Add the string to the path
path.AddString(buf,wcslen(buf),&fontFamily,FontStyleRegular,36,PointF(10.0f,10.0f),&strformat);

// Draw the outline first
Pen pen(Color(255,0,255),4);
graphics.DrawPath(&pen, &path);
// Draw the text by filling the path
SolidBrush brush(Color(0,128,128));
graphics.FillPath(&brush, &path);

Before you rush to make your own outline library, I have to tell you one pitfall of GDI+. GDI+ cannot handle Postscript OpenType fonts; GDI+ can only handle TrueType fonts. I have searched for a solution and found Sjaak Priester's Make GDI+ Less Finicky About Fonts. His approach is to parse the font file for its glyph and draw its outline. Sadly, I cannot use his code as his library is using the GNU license as I want to make my code free for all to use. I racked my brains for a solution. Since GDI(not GDI+) can display Postscript OpenType fonts and GDI supports path extraction through BeginPath/EndPath/GetPath. I decided to use just that to get my path into GDI+. Below is the comparison of the GDI+ path and GDI path. Note: Both are rendered by GDI+, it is just that their path extraction is different; one is using GDI+ while the other is using GDI to get the text path.

The top one is using GDI+ path and the bottom one is using GDI path. Looks like GDI paths are inferior and inaccurate. But GDI paths can do rotated text trick, like below, which GDI+ cannot do because GDI GraphicsPath's AddString takes in a FontFamily object, not a Font object. My OutlineText class provides the GdiDrawString method if you have the need to use PostScript OpenType fonts. The effect below is a Franklin Gothic Demi font, size 36, Italic text rotated 10 degrees anti-clockwise.

Here is how to do text outline using my library.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

OutlineText text;
text.TextOutline(Color(0,128,128),Color(255,0,255),4);
text.DrawString(&graphics,&fontFamily,FontStyleRegular, 36, L"CodeGuru Is The Best!", Gdiplus::Point(10,10), &strformat);

Here is how to do rotated text using GDI

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = -MulDiv(36, pDC->GetDeviceCaps(LOGPIXELSY), 72);
lf.lfWeight = FW_NORMAL;
lf.lfItalic = TRUE;
lf.lfOrientation = 100; // 10 degrees
lf.lfEscapement = 100; // 10 degrees
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
wcscpy_s(lf.lfFaceName, L"Arial");

// create and select it
CFont newFont;
if (!newFont.CreateFontIndirect(&lf))
	return;
CFont* pOldFont = pDC->SelectObject(&newFont);

pDC->TextOut(10,80,L"CodeGuru Is The Best!", wcslen(L"CodeGuru Is The Best!"));
// Put back the old font
pDC->SelectObject(pOldFont);

Here is how to do rotated text outline using my library.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

OutlineText text;
text.TextOutline(Color(255,0,0),Color(255,255,255),4);

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = -MulDiv(36, pDC->GetDeviceCaps(LOGPIXELSY), 72);
lf.lfWeight = FW_NORMAL;
lf.lfItalic = TRUE;
lf.lfOrientation = 100; // 10 degrees
lf.lfEscapement = 100; // 10 degrees
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
wcscpy_s(lf.lfFaceName, L"Franklin Gothic Demi");

text.GdiDrawString(&graphics,&lf,L"CodeGuru Is The Best!",Gdiplus::Point(10,80));

Outline Text, Part 1

Double Outline

[DblOutline.PNG]

I also provide a way for the users to do double outline. The Chinese tabloids in my home country, Singapore, like to do double outline text in their news headlines. Generally, to make double outline text, you have to render the bigger outline first, then the smaller outline, followed by the text.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

Font font(pDC->GetSafeHdc(), &lf);
GraphicsPath path(FillModeWinding);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t buf[] = L"CodeGuru Is The Best!";

// Add the string to the path
path.AddString(buf,wcslen(buf),&fontFamily,FontStyleRegular,36,PointF(10.0f,10.0f),&strformat);

// Draw the outer outline first
Pen pen2(Color(128,128,128),8);
graphics.DrawPath(&pen2, &path);
// Then draw the inner outline 
Pen pen(Color(255,0,255),4);
graphics.DrawPath(&pen, &path);
// Draw the text but filling the path
SolidBrush brush(Color(0,128,128));
graphics.FillPath(&brush, &path);

Here is how to do double text outline using my library.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

OutlineText text;
text.TextDblOutline(Color(0,128,128),Color(255,0,255),Color(128,128,128),4,4);
text.DrawString(&graphics,&fontFamily,FontStyleRegular, 36, L"CodeGuru Is The Best!", Gdiplus::Point(10,10), &strformat);

Text Glow

[TextGlow.JPG]

Above image is an example is the text glow effect. It is easy to achieve this effect: Just set the drawing pen for the outline with a very low alpha value and keep drawing this outline while increase the width of this pen progressively. Below, it is the generic GDI+ code to do it.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

Font font(pDC->GetSafeHdc(), &lf);
GraphicsPath path(FillModeWinding);
FontFamily fontFamily(L"Arial");
StringFormat strformat;
wchar_t buf[] = L"CodeGuru Is The Best!";

// Add the string to the path
path.AddString(buf,wcslen(buf),&fontFamily,FontStyleRegular,36,PointF(10.0f,10.0f),&strformat);

// Then draw the outline progressively bigger, using a for loop
for(int i=1;i<8;++i)
{
	Pen pen(Color(32,255,0,255),i);
	graphics.DrawPath(&pen, &path);
}

// Draw the text but filling the path
SolidBrush brush(Color(0,128,128));
graphics.FillPath(&brush, &path);

Here is how to do text glow using my library.

using namespace Gdiplus;
Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);

OutlineText text;
text.TextGlow(Color(255,255,255),Color(12,0,0,255),8);

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = -MulDiv(36, pDC->GetDeviceCaps(LOGPIXELSY), 72);
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
wcscpy_s(lf.lfFaceName, L"Vesta");

// Vesta is a Postscript OpenType font so have to use GDI, not GDI+ to render.
text.GdiDrawString(&graphics,&lf,L"CodeGuru Is The Best!",Gdiplus::Point(10,80));

Demo program

I have made a simple demo application which you can play around.

[DemoScreenshot.JPG]

Outline Text using DirectWrite

Below is Tom Mulcahy(Microsoft's developer of Direct2D for Windows 7) reply email to me.

(Courtesy of Tom Mulcahy) The way to do this is to get the text contents as an ID2D1GeometrySink (See IDwriteFontFace::GetGlyphRunOutline). You can then call ID2D1RenderTarget::DrawGeometry to draw the outline of the text (specifying any color and width you want). Next call ID2D1RenderTarget::FillGeometry to fill the text (again you can specify any color you want).

Conclusion

In this part 1 of the article series, we have learnt how to do text effect with single outline, double outline and text glow. I am planning to port this library to .Net and do a fast cached rendering version, suitable for HD video rendering; I will be updating this article often. So be sure to check out this article when it is updated! This is 3 part article. In the 2nd installation of the article, I'll show you how to render text shadows and in the final installation, I'll show you how to do really advanced and cool outline text effects! I hope you have enjoyed reading this article as much as I have enjoyed writing it. Any feedbacks or bugs, please do let me know, so that I can improvise on this article. I'll leave you a preview image of what to expect in the part 2 of the article.

[Shadow.PNG]

Please read OutlineText, Part 2 article here if you are interested to know about implemeting shadows for the text. Also visit the Codeplex for a more updated source code download.

References

History

  • 8th October 2009: Fixed the bug of exceptional sharp edges in some fonts by using rounded line joins. And making the demo application more user friendly by remembering the previous font and colors selected and take away the annoying exit application prompt.
  • 30th April 2009 : First Release on Codeguru


About the Author

Wong Shao Voon

Rather than to write an accolade of skills which I currently possess, these are the technologies, I am currently exploring:

  • 3D Graphics(Lighting and Shadow)
  • ASP.NET MVC 4
  • C++14
  • Garbage Collection
  • GPU Computing
  • H.264 video
  • HTML5 and CSS3
  • iOS
  • SQL Server 2012

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds