Advanced Basics

Creating Text Images On the Fly with GDI+

Ken Spencer

Code download available at:AdvancedBasics0309.exe(193 KB)

Q I am building a Windows® Forms application and need to be able to create graphical text on the fly. Basically, I want to create an image, add text to it, then use the new image later in the app.

Q I am building a Windows® Forms application and need to be able to create graphical text on the fly. Basically, I want to create an image, add text to it, then use the new image later in the app.

A The first step in solving this problem is pretty easy. I created a simple form that allows you to enter a string of text, then enter the starting point for the text in the resulting output. After you enter the string, the code will create an image from it that is then placed in one or both of the textboxes on the form (see Figure 1).

A The first step in solving this problem is pretty easy. I created a simple form that allows you to enter a string of text, then enter the starting point for the text in the resulting output. After you enter the string, the code will create an image from it that is then placed in one or both of the textboxes on the form (see Figure 1).

Figure 1 Creating an Image in a Textbox

Figure 1** Creating an Image in a Textbox **

The application creates a bitmap image, then draws the string on the bitmap. The bitmap can then be displayed in a PictureBox. Let's take a look at the code, then explore GDI+a bit more.

The first step in creating this form was to define a set of variables. The following lines are from frmGraphics1.vb. The drawBrush and fillBrush variables are used to draw the string and to fill the rectangle that sits behind the string. The drawFont variable is a reference to the font I am using, MySize is used with the MeasureString method, and the last variable, ImageFortext, is the image used for the text:

Dim drawBrush As New SolidBrush(Color.Black) Dim fillBrush As New SolidBrush(Color.White) Dim drawFont As New Font("Arial", 16) Dim iX, iY As Integer Dim MySize As SizeF Private ImageFortext As Bitmap

Now let's walk through the code in cmdCreateText_Click. This code does not display the image; it simply creates it from text. The first line creates a reference to the Graphics class:

Dim MyGraphics As Graphics

Next, a bitmap image is created:

ImageFortext = New Bitmap(773, 1060, PixelFormat.Format32bppArgb)

This image is created at an arbitrary size of 773 pixels wide and 1060 pixels high. Ideally you will want to calculate the image size based on the actual text, the font being used, and some margin space. You can vary the size by changing the first two variables to the Bitmap constructor. The last parameter to the constructor specifies the pixel format used by the image, in this case 32 bits per pixel.

Next, MyGraphics is set to a new graphics object that is created from the image that I just made:

MyGraphics = MyGraphics.FromImage(ImageFortext)

Then the MeasureString method is called to determine the physical size of the text the user entered:

MySize = MyGraphics.MeasureString(txtGraphicText.Text, drawFont)

Now I can fill a rectangle with an image that is the same size as the text, as shown here:

MyGraphics.FillRectangle(fillBrush, _ New Rectangle(iX, iY, MySize.Width, MySize.Height))

The rectangle is created using the white fillBrush. You can do all sorts of things with the FillRectangle method. Since you can pass this method a brush of your choosing, you can create solid or hatched fills and more, drawing almost any type of background for your text.

Finally, I can place the string on the image by calling DrawString:

MyGraphics.DrawString(txtGraphicText.Text,drawFont,drawBrush,iX,iY)

In this case, I'm drawing a simple horizontal string on top of the background. Of course, I could draw the string vertically as well.

Now that the image is all set, I can call DrawImage to generate it:

MyGraphics.DrawImage(ImageFortext, New Point(0, 0))

Next, I dispose of the Graphics object to free up resources, then enable the buttons to show the text:

MyGraphics.Dispose() cmdShowText.Enabled = True cmdShowText2.Enabled = True

Now, let's look at how to display the new text image. My first experiments led me to try creating a new Graphics object and set it to the image. Then I stumbled on the simple but elegant solution shown here. All you need to do is set the Image property of the PictureBox to the new image (ImageFortext) and the new text shows up in the image. The surrounding code simply handles the suspension and resumption of the layout and sets the size of the control equal to the part of the image you want to show:

With picMyText .SuspendLayout() .Image = ImageFortext .Size = New Size(MySize.Width + iX, MySize.Height + iY) .ResumeLayout() End With

Now you can reuse the image at any place in this application by using the ImageForText image. The Image class also contains a Save method, which allows you to save your image to a file or almost anywhere else (such as the Response object in ASP.NET or a stream).

Now let's look at a more practical way of using the functionality I've shown you so far. In this case, I want to pull a list from a database and display the retrieved text as an image with a checkbox beside it. The code I have already created is remarkably close to solving this problem.

First, I created a new user control named ctrlCheckImages. The control has the following private variables:

Public pvtChecked As String Public pvtName As String Dim drawBrush As New SolidBrush(Color.Black) Dim fillBrush As New SolidBrush(Color.White) Dim drawFont As New Font("Arial", 16) Dim MySize As SizeF Dim pvtRectangleWidth As Integer

In addition, it has three public variables: TextWidth, Checked, and ItemName. The first two properties are standard implementations that set or retrieve values from private variables. The ItemName property is slightly different as it also calls the CreateImage procedure when the value is set:

Public Property ItemName() As String Get Return pvtName End Get Set(ByVal Value As String) pvtName = Value CreateImage(pvtName) End Set End Property

I also added a checkbox to the user control, placing it in the far left position. I set the size to 16, 24, and the location to 0, 0. The Text was set to an empty string. This checkbox has one event (chkIndicator_CheckedChanged), which sets the pvtChecked variable to the state of the control:

pvtChecked = chkIndicator.Checked

Next, I implemented a procedure to create the text (CreateImage). This procedure takes one argument, of type PaintEventArgs, because it must be called in the Paint event. This code is similar to the CreateImage code, with a few variations. First, the Graphics object is now the control's surface instead of an image. Why? In this case, I am simply outputting an image to the control, so there is no need to incur the overhead of creating an image in memory first when I can just manipulate the control directly.

The MyGraphics variable is set to reference the control:

Dim MyGraphics As Graphics Dim TextString As String MyGraphics = e.Graphics

This code also picks up the text string from the private variable instead of pulling it from the textbox:

TextString = pvtName

Then the code in Figure 2 paints the control. Now, I can draw the string on the control:

MyGraphics.DrawString(TextString, drawFont, drawBrush, 26, 0)

Next, I reset the control to approximately the same size as the text string, as shown in the following line:

Me.Size = New Size(pvtRectangleWidth + 26, MySize.Height)

Finally, I dispose of the Graphics object to free up resources:

MyGraphics.Dispose()

Now let's take a look at the code in frmGraphicsStringFromDB.vb that uses this control. The ShowData procedure creates several variables, including the GraphicsChecker variable which is a reference to the new control:

Dim yloc, yincr As Integer Dim rw As DataRow Dim GraphicsChecker As ctrlCheckImages Dim i As Integer

Figure 2 Painting the Control

MySize = MyGraphics.MeasureString(TextString, drawFont) If pvtRectangleWidth > 0 Then MyGraphics.FillRectangle(fillBrush, _ New Rectangle(0, 0, _ pvtRectangleWidth, MySize.Height)) Else pvtRectangleWidth = MySize.Width MyGraphics.FillRectangle(fillBrush, _ New Rectangle(0, 0, _ MySize.Width, MySize.Height)) End If

Next, I set several variables:

yloc = 50 yincr = 26.2 i = 0

As the code iterates through the datatable, it creates and initializes a new GraphicsChecker for each record. The new control is then added to the Controls collection of the form. It doesn't have to work with the image directly in this example because the image handling is done by the control. The rest of the code is maintenance or cleanup code, as shown in Figure 3.

Figure 3 Maintenance

For Each rw In dsCustomer.Tables(0).Rows GraphicsChecker = New ctrlCheckImages With GraphicsChecker .TextWidth = 380 .Checked = False .ItemName = rw("CompanyName").ToString .Location = New Point(20, yloc) End With Me.Controls.Add(GraphicsChecker) yloc += yincr GraphicsChecker = Nothing If i = NoOfItemsPerForm Then Exit For End If Next

GDI+ is a great addition to your toolkit for building applications. You can add graphics to your application with a few lines of code and present a rich interface to your users. But before you decide to use images everywhere, just remember that they come at a price. Every time you use GDI+ to output a different graphic there is overhead involved. Consider the example in which I used the control with GDI+ to output the checkbox and string. In the code download, there is another file that implements the sample control (frmGraphicsStringFromDB2). This form has a control that does not use DrawString, instead employing a label control for the text. It still uses FillRectangle to define the text area but because it isn't calling an image, the difference in performance is dramatic, as you will see when you run it.

No matter how cool a UI element idea may seem, never forget to consider performance. I remember a project in which the application would write out a simple line of text as it processed items. Performance improved dramatically when I stopped writing to the console. All output is expensive, so keep that in mind.

Send your questions and comments for Ken to  basics@microsoft.com.

Ken Spencerworks for 32X Tech (https://www.32X.com), where he provides training, software development, and consulting services on Microsoft technologies.