Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating fancy text effects with C#

0.00/5 (No votes)
16 Mar 2006 1  
Code to create cool looking text for use in your application.

Sample Image - FancyText.png

Introduction

Our designer gave me some cool Photoshop screen drafts of what she thought our application ought to look like. The drafts had several examples of where she had made text with a halo outline around the words. Like all good designers, she had sliced the images up into discrete sections, allowing me to do the integration easily. The problem with this approach was that it was time consuming to integrate each image she gave me, and when we wanted to localize the product later, we would need to redo them all. I needed a way to generate these on the fly.

The Plan

WinForms' System.Drawing.* provides very powerful ways of creating images with alpha blending. The plan was to create a bitmap for the text. The text would be smeared around in a background color on the bitmap, and then finally written in the middle in the foreground color on the bitmap. Alpha blending would be applied to the background, smeared, version of the text so that it looked feathery towards the edges and could be rendered over any other background. (Alpha valued colors, added on top of alpha valued colors will increase the intensity, so the very edges should look dim, and the closer to the text you are, the more intense the background color will be).

I expected this to take a little while to do, so I planned on having a function return the bitmap for the final text image so that I could squirrel it away and use that as a cached version of the text from that point onwards, thus only suffering the performance hit once.

This seemed like a workable solution, so I set about implementing it...

The Implementation

This was going to take some brushes, some bitmaps, some graphics contexts etc., and we may call this function hundreds of times, so judicious resource management would be important. For those that don't know, when a managed object goes out of scope, it is marked as "unreferenced" to be garbage collected automatically, later in time. When? You may have no idea if you don't explicitly call garbage collection yourself (not necessarily recommended if you use things like weak references, etc., but that is beyond the scope of this article). These managed objects, like the SolidBrush class, for instance, are, in real life, managed wrappers on GDI+ unmanaged objects, and these objects behind the scenes may consume valuable system resources and memory. Thus, you should not just allow a Brush or Pen or Bitmap or any of the myriad of these wrappers on unmanaged drawing objects simply to fall out of scope in the hope they will be cleaned up soon, as they will insidiously eat precious resources. You need to call Dispose on each of them to clean up the unmanaged resources. A preferred method is to use the using construct. "using" will cause the object's Dispose method to be called when you leave the using() scope (which will, in turn, free the underlying GDI+ object). You could call Dispose yourself at the end of your function, but if you exited the function before calling Dispose, or if you had an exception before Dispose(), then you would have the same resource problem you had before. using ensures that no matter what happens to cause the control to leave the scope of the using() statement, the object's Dispose method would be called.

using (SolidBrush brFore=new SolidBrush(clrFore))
{
  ... use the brush inside of here
}

In order to make the containing bitmap, we will need to know how big the text will be in pixels. We use the Graphics method MeasureString to determine this. MeasureString comes in various flavors, some taking into account alignment and spacing of text etc. We use the basic call as we don't intend on setting any exotic flags when we finally call Graphics.DrawString. From the returned size, we can make a bitmap that will contain a single rendering of the text which we will use as a basis for rendering onto a second, destination bitmap, several times, to make the blurred background.

SizeF sz=g.MeasureString(strText, fnt);

GDI+ is built for both speed and beauty, and by default, it picks a happy medium. There are properties you can set on a Graphics object that will ensure you get the best possible rendering. We use SmoothingMode, InterpolationMode, and TextRenderingHint to control the level of output we require.

gBmp.SmoothingMode=SmoothingMode.HighQuality; 
gBmp.InterpolationMode=InterpolationMode.HighQualityBilinear; 
gBmp.TextRenderingHint=TextRenderingHint.AntiAliasGridFit;

From the bitmap, we create a graphics context (Graphics.FromImage). This will allow us to draw the string onto the bitmap directly. We make some brushes that we know we will need a little later here, so that we can keep all the using statements bunched together, and avoid overly nesting this function, to aid readability. Also, we fully expect this function to always complete all the way through, so the marginal overhead over disposing of this object exactly when they are not needed is not worth the expense of readability and maintainability of this function. Note the alpha value of 16 on the brBack brush, this is a very transparent value, but will be overlaid many times as we smear the background.

using (Bitmap bmp=new Bitmap((int)sz.Width,(int)sz.Height)) 
using (Graphics gBmp=Graphics.FromImage(bmp)) 
using (SolidBrush brBack=new 
  SolidBrush(Color.FromArgb(16,clrBack.R, 
  clrBack.G, clrBack.B))) 
using (SolidBrush brFore=new SolidBrush(clrFore)) 
{
    ...
}

We now create another image, made bigger by blurAmount, to accommodate the smearing, and proceed to get a Graphics from that so that we can render on the first bitmap we created onto it, and draw it blurAmount times in the X direction for every blurAmount times in the Y direction. This rectangular blur approximates a more traditional rounded blur as the alpha values towards the outside are sufficiently low as to make it feather; to be more accurate, you would base the rendering in a circle from the midpoint. The simplification is justified in this case, again for readability, ease of coding, and because the difference would be slight.

bmpOut=new Bitmap(bmp.Width+blurAmount,bmp.Height+blurAmount);
...
// smear image of background of text about

// to make blurred background "halo" 

for (int x=0;x<=blurAmount;x++) 
   for (int y=0;y<=blurAmount;y++) 
      gBmpOut.DrawImageUnscaled(bmp,x,y);

After rendering the blur, we finally render the actual text again, in the center (blurAmount/2 offset in both X & Y positions) in the foreground color.

// draw actual text 

gBmpOut.DrawString(strText, fnt, brFore, 
        blurAmount/2, blurAmount/2);

Using the code

Here is how you would call the code to generate an image for some text, and how you would render the final fancy text image onto a Windows form:

public class Form1 : System.Windows.Forms.Form 
{ 
  private Bitmap _bmpText; 

   public Form1() 
   { 
    this.BackColor = System.Drawing.Color.IndianRed; 
    this.ClientSize = new System.Drawing.Size(358, 126); 
    using (Font fnt=new Font("Arial", 20, FontStyle.Bold)) 
       _bmpText = (Bitmap) FancyText.ImageFromText("Hello Code" + 
                   " Project Fans!", fnt, Color.Green, Color.Yellow); 
  } 

  protected override void OnPaint(PaintEventArgs e) 
   { 
    e.Graphics.DrawImageUnscaled(_bmpText, 10, 40); 
  }
}

History

  • Created - 03/16/06.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here