TECHNOLOGIES: GDI+ | Enumerations | Reflection
Learn to generate graphics dynamically with GDI+.
By Ken Getz
At a recent conference, I rushed through an hour-long whirlwind tour of the drawing features provided by the .NET Framework - referred to as GDI+ - within the context of Windows Forms development. At the end of the session one of the attendees asked, "How can I apply this to building Web sites with ASP.NET?" Being immersed in Windows Forms at the moment, the correct answer simply didn't bubble to the surface. The answer, of course, is that GDI+ lets you dynamically generate graphics that can be displayed in response to Web page requests.
GDI+ can play an important part in ASP.NET Web development, and this article introduces several of the important classes involved in creating and displaying graphic images. Before digging much deeper, you must understand that GDI+ is a set of wrapper classes that let you interact programmatically with Windows-provided drawing services. In the .NET Framework, classes within the System.Drawing namespace provided this support, and if you're creating a Web application using Visual Studio .NET - in either C# or VB .NET - the project template you start with most likely includes a reference to the necessary assembly.
When using GDI+ in Windows applications, you generally utilize members of the GDI+ object family as you draw the form - that is, from or in reaction to the form's Paint event (you even can interact with the drawing process as the form paints). When creating Web applications, this interactive approach does not make sense because the drawing needs to be done on the server, where your code (and the .NET Framework) is running. To make use of GDI+ in Web applications, you need to generate some sort of dynamic graphic image as you handle the page request and supply the image in the Web response.
To demonstrate some of the simpler GDI+ capabilities, I'll use a simple site that allows you to specify font and text characteristics, which, in turn, allow the page to generate a bitmap that contains the requested text in the requested font programmatically. (You can download the sample code referenced in this article.) Once ASP.NET renders the page, you (or a user) can copy the bitmap to the Windows Clipboard and paste it into another document or application. Figure 1 shows the page after it has generated the sample bitmap corresponding to the selections on the page.
Figure 1. You can use GDI+ on the server side to generate graphic images sent to the client browser in the Web response.
Lay Out the Request
The sample page consists of a table and a Literal control into which the sample page injects HTML for an tag, setting the source as a second page that actually generates the bitmap. See Figure 2.
Figure 2. The sample page consists of a simple table and a Literal control.
This sample page displays enumerated values in two CheckBoxList controls (Style and String Format), using the FillListWithEnum procedure shown here:
Private Sub FillListWithEnum( _
ByVal lc As ListControl, ByVal typ As Type)
If typ.IsEnum Then
lc.DataSource = System.Enum.GetNames(typ)
The page's Load event handler calls this procedure, passing in two different enumerated types:
The page also fills an ArrayList with all the available colors, then binds a DropDownList control to that ArrayList. The following procedure fills the ArrayList (I can't take credit for this procedure - thanks to my friend Andy Baron who utilized .NET Reflection to dig into the required details and retrieve the list of colors):
Sub FillColorArray(ByVal colorNames As ArrayList)
Dim pi As System.Reflection.PropertyInfo
Dim aPropInfo() As System.Reflection.PropertyInfo
aPropInfo = GetType(System.Drawing.Color).GetProperties
For Each pi In aPropInfo
If pi.PropertyType.Name = "Color" Then
' Skip transparent. It won't work here.
If pi.Name <> "Transparent" Then
Code in the page's Load event handler fills the two DropDownList controls that contain colors:
Dim al As New ArrayList()
ddlTextColor.DataSource = al
ddlBackColor.DataSource = al
Finally, the FillFontList procedure uses the .NET Framework's InstalledFontCollection class to retrieve a list of all the fonts available on the server. The page's Load event handler then calls this procedure to fill a DropDownList control with the list of fonts:
Private Sub FillFontList(ByVal lc As ListControl)
Dim ifc As New InstalledFontCollection()
ffs = ifc.Families
lc.DataSource = ffs
lc.DataTextField = "Name"
Once you select from the list of choices, click on Create Bitmap to run the code shown in Figure 3. This code retrieves the selected choices and generates a request to a second page, DrawString.aspx, which actually does the work (and finally gets us to the GDI+ features). This procedure includes calls to other subsidiary procedures that work with enumerated values (GetFontStyle and GetStringFormatFlags), but, in general, the code simply generates a query string containing all the parameters selected.
Private Sub btnCreate_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Dim strFontName As String = ddlFont.SelectedItem.Text
Dim strText As String = txtSample.Text
' Get the font size.
Dim intFontSize As Integer = 12
If txtFontSize.Text <> String.Empty Then
intFontSize = CType(txtFontSize.Text, Integer)
' Get the font style.
Dim fs As FontStyle = GetFontStyle(cblStyle)
Dim sfmtFlags As StringFormatFlags = _
' Generate the necessary HTML tag.
ltImage.Text = String.Format( _
strText, strFontName, intFontSize, _
CType(fs, Integer), _
Figure 3. This code generates the tag the page uses to retrieve the bitmap from the second page, DrawString.aspx.
Retrieve the Image
To display the image containing the formatted text, the sample page needs some way to request the image. Of course, the page simply could run code to generate a bitmap, store the file on disk, and retrieve that file in response to the page request. Although this technique does work, it utilizes an inefficient, non-scalable solution. The sample uses a better solution popular already among Web developers.
In this case, the page sets the src attribute of the tag to be the address of a second page, passing in parameter information within the query string. When the first page attempts to render the image control, it must resolve the request for the image, which allows the second page to load and render its own image utilizing the passed-in parameters. It's here, in the second page (DrawString.aspx), that all the work involving GDI+ takes place. Note that the code near the end of Figure 3 does the work of creating the tag:
ltImage.Text = String.Format( _
strText, strFontName, intFontSize, _
CType(fs, Integer), _
Within DrawString.aspx, code in the page's Load event handler generates a Bitmap object (more on this in the next section) named bmp and saves the bitmap to the page's output stream using this code:
Response.ContentType = "image/gif"
By setting the ContentType of the Response object to "image/gif" and saving the bitmap into the OutputStream property, the DrawString.aspx page provides the bitmap as the source for the image control on the original page. This technique - setting an image control's source to be a separate page that generates or retrieves the actual image - is one you will find used in many sites and demonstrations.
Generate the Bitmap
Finally, we get to the GDI+ code. If you dig into the documentation on the System.Drawing namespace, you'll see that this namespace includes many objects - covering them all would take an entire book or two. On the other hand, writing articles about each of the objects would be overkill. Instead, this example uses several basic GDI+ objects to get you started with GDI+ and supply you with some ideas as to how you can use these objects in your own applications.
Rather than focusing on the objects, however, the remainder of this article works through the Page_Load procedure in order, starting with DrawString.aspx, describing each GDI+ object as it gets used. The procedure starts by retrieving all its parameters from the query string:
Dim strFontName As String = _
Dim strText As String = _
Dim clrBack As Color = _
Dim clr As Color = _
Dim intFontSize As Integer = _
Dim fs As FontStyle = _
Dim sfmtFlags As StringFormatFlags = _
Among the other simple types, the procedure retrieves values into FontStyle and StringFormatFlags enumerated types. The code uses the FontStyle value only later, when creating a font. But to use the StringFormatFlags value, the code must create a StringFormat object and assign the StringFormatFlags value into the FormatFlags property:
Dim sfmt As New StringFormat()
sfmt.FormatFlags = sfmtFlags
The StringFormat object controls the formatting of text in GDI+, and it includes members that control alignment, trimming, and formatting of text drawn using GDI+. In this case, the code creates a StringFormat object and sets its FormatFlags property for later use. (Although the StringFormatFlags enumeration contains several different values as shown in Figure 1, only a few will affect the formatting in this example. The DirectionRightToLeft and DirectionVertical flags change the appearance of the bitmap; the rest of the flags affect string formatting in ways that don't change the formatting of the sample bitmap.)
If you're going to work with text, you'll need a Font object that describes the text's appearance. The sample procedure creates a new Font object using parameters passed in the page's query string:
Dim fnt As New Font( _
strFontName, intFontSize, fs, GraphicsUnit.Point)
The sample code uses this Font object several times. It uses the Font object first to calculate the size of the required bitmap, and later it uses the same Font object to render the text in the bitmap. (The Font class, like most other GDI+ objects, provides different constructors. Refer to the .NET Framework documentation for the full details. Obviously, an overview such as this one can show only a single constructor for each class used.)
Next, the code must calculate the size of the requested text, using the requested font family, size, and formatting. Calculating the size requires calling the MeasureString method of a Graphics object, and that, in turn, requires the creation of the Graphics object. For all intents and purposes, you can think of a Graphic object as your "logical" canvas - that is, it provides the object that supplies the methods necessary to "paint" an image. The .NET Framework presents many ways to generate a Graphics object, but you'll note that the Graphics class provides no constructor - instead you must call a method of some other object to create one. The Graphics class provides several shared methods (FromHdc, FromHwnd, and FromImage) to generate a new Graphics object from various sources. This example uses the FromImage method of a Bitmap object to generate the Graphics object necessary to create the drawing.
To generate the Graphics object, however, you'll first need a Bitmap object. The Bitmap object represents an image in memory. The specific serialization format (BMP, JPEG, GIF, and so on) isn't an issue until you attempt to save the bitmap, so that need not factor into the Bitmap object's creation. Because the code doesn't yet know how large the actual bitmap needs to be, it creates a 1x1 bitmap like this:
Dim bmp As New Bitmap(1, 1)
Given that tiny bitmap, the code can generate a Graphics object:
Dim g As Graphics = Graphics.FromImage(bmp)
Once the code has generated its Graphics object, it no longer needs the Bitmap object. Because a Bitmap object (like most GDI+ objects) creates a wrapper around an unmanaged Windows resource, it's important that your code release the reference manually and as soon as possible. To do this, you need to call the Dispose method of each GDI+ object you create. (If you neglect to dispose of the object, the garbage collection will get to it sooner or later, but it's best to clean things up yourself.)
Now that the code has its Graphics object, it can call the object's MeasureString method to calculate the size for the output bitmap and dispose of the Graphics object:
Dim szf As SizeF = g.MeasureString( _
strText, fnt, New PointF(0, 0), sfmt)
The MeasureString method provides multiple overloaded versions, but the one used here requires you to supply text, a Font object, a starting location for the text within the measuring region, and a StringFormat object describing the text's format. The only new types used here are the PointF and SizeF types (the PointF type provides the starting location, and the SizeF type contains the results of calling the MeasureString method). Note that each of these is a structure rather than a class. This means you don't need to use the New keyword when creating a variable of these types unless you need to call the constructor for the type as with this example's PointF value. A PointF value contains location information, and a SizeF value contains size information. These types contain floating-point values; you'll find corresponding integer types, Size and Point, in the .NET Framework.
The code creates a new Bitmap object when provided the size for the bitmap. Before generating the Bitmap, the sample creates a Rectangle value describing the coordinates of the Bitmap. (Again, a Rectangle is a structure, not a class; a value type, not a reference type. You needn't use the New keyword unless you need to call the type's constructor, as does the sample procedure.) You'll find that the overloaded procedures in the System.Drawing namespace almost always allow you to specify coordinates either as individual values, as a pair of Point/Size values, or as a Rectangle value. In this case, because the code has the size of the bitmap in a SizeF structure, there's no easy way to convert from a SizeF to a Size structure, and the Bitmap class's constructor requires integer values as opposed to floating-point values. The code creates the rectangle and converts the coordinates into integers manually:
Dim rct As New Rectangle( _
0, 0, _
CType(szf.Width, Integer), _
Next, the code creates a new Bitmap using the correct size, then generates a new Graphics object based on the new Bitmap and fills the bitmap with the appropriate color:
bmp = New Bitmap(rct.Width, rct.Height)
g = Graphics.FromImage(bmp)
g.FillRectangle(New SolidBrush(clrBack), rct)
The Graphics object's FillRectangle method fills a rectangular area with a solid color, a hatched pattern, or a gradient. In each case, it uses an object that inherits from the base Brush class to determine the fill type (SolidBrush, HatchBrush, LinearGradientBrush, or PathGradientBrush). You can think of a Brush object as a logical paintbrush - a tool that fills a region with paint. The .NET Framework provides a group of pre-filled brushes (Brushes.Red, for example), but this example requires a new SolidBrush object, which passes in the supplied color as a parameter to the constructor. The FillRectangle method also accepts a rectangle to be filled as well. (You'll find many methods like this in the Graphics class: DrawString, DrawEllipse, and so on.)
The next code snippet needs to calculate the starting point within the bitmap for the text. Normally, the text would start at the point (0, 0) (that is, 0 pixels horizontally and 0 pixels vertically from the upper-left corner of the bitmap). If you've specified a right-to-left orientation, the text must begin in the upper-right corner. The following code checks the appropriate bit within the format flags and changes the starting point if the DirectionRightToLeft flag is set:
Dim pt As New Point(0, 0)
If (sfmtFlags And _
StringFormatFlags.DirectionRightToLeft) = _
pt = New Point(rct.Width, 0)
Finally, it's time to draw the text. The code uses the DrawString method of the Graphics object to do its work, selecting one of the several overloaded versions of the method:
g.DrawString(strText, fnt, _
New SolidBrush(clr), pt.X, pt.Y, sfmt)
This version of the method allows you to specify text, a font, a brush to paint the text, a location, and formatting information. (Because you specify a Brush when drawing text, it's simple to create text using any type of brush, including hatch and gradient brushes. You easily could modify the sample to include support for these fancier brushes.)
This brings us again to the code that saves the bitmap to the output stream, here followed by code that releases all unmanaged resources:
Response.ContentType = "image/gif"
Now you've investigated the entire sample, line by line. Take some time to try out the sample application and a few bitmaps. Copy and paste a bitmap from the sample page to some other application and you'll see the benefit of the sample itself. (You could use a painting program such as Windows Paintbrush to get the same results, but this technique certainly is simpler if you only want text in a bitmap.) This example showed several different GDI+ objects (Graphics, Bitmap, Font, Brush, Rectangle, Point, and Size) and methods (FromImage, MeasureString, and DrawString), and there are many more to work with. Once you've worked through this example, try modifying it, adding support for other graphic objects and methods. Anytime you need graphics generated dynamically as part of a site, you'll find these techniques useful.
The sample code in this article is available for download.
Ken Getz is a senior consultant with MCW Technologies and splits his time between programming, writing, and training. He specializes in tools and applications written in Visual Studio .NET and Visual Basic, and he is co-author of Access 2002 Desktop Developer's Handbook (Sybex) as well as the training materials for AppDev's ASP.NET, Access 97 and 2000, Visual Basic 5.0, and Visual Basic 6.0 classes. He speaks frequently at technical conferences and is a contributing writer for asp.netPRO. E-mail Ken at mailto:[email protected].
Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.