24

Generating graphics on the fly

Share on TwitterShare on TumblrSubmit to StumbleUponSave on DeliciousDigg This

Generating a graphic

Before burdening us with the ballast of ASP.NET, we do a dry run with a simple command line program – and then use this source code as the base of our ASP.NET script. The difference lies in the fact that the command line program saves the graphic to a file whereas the ASP.NET script sends it directly to the client.

Now, what is our example program to do? As always, we start with the well loved “Hello World” program – this text shall be output into a graphics file and the graphic shall be exactly the size of the text “Hello World” in the currently selected font and font size (therefore, simply generating an oversize bitmap won’t count).

The following Script (pagecounter.cs) is a typical simple command line program: disregarding the class wrapped around it (that has to be), there only is the function Main, which is called when the program is run. And this is where our graphics generating code resides..

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

public class CTestBitmapFunctionality
{
  public static void Main()
  {
    Bitmap newBitmap = null;
    Graphics g = null ;

    try 
    {
        Font fontCounter = new Font("Lucida Sans Unicode", 12);

        // calculate size of the string.
        newBitmap = new Bitmap(1,1,PixelFormat.Format32bppArgb);
        g = Graphics.FromImage(newBitmap);
        SizeF stringSize = g.MeasureString("Hello World", fontCounter);
        int nWidth = (int)stringSize.Width;
        int nHeight = (int)stringSize.Height;
        g.Dispose();
        newBitmap.Dispose();

        newBitmap = new Bitmap(nWidth,nHeight,PixelFormat.Format32bppArgb);
        g = Graphics.FromImage(newBitmap);
        g.FillRectangle(new SolidBrush(Color.White), new Rectangle(0,0,nWidth,nHeight));

        g.DrawString("Hello World", fontCounter, new SolidBrush(Color.Black), 0, 0);
        newBitmap.Save("c:\\test.png", ImageFormat.Png);
    } 
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
    finally 
    {
        if (null != g) g.Dispose();
        if (null != newBitmap) newBitmap.Dispose();
    }
  }
}

What does this source code do? In any case, the result is the following graphic test.png residing on drive c:

How is this graphic created? To find out, we need a closer look at the source code. Our main prerequisite was that the graphic must be the same size as the text “Hello World” in the font to be used for rendering. Therefore, I first have to calculate the size of the text and for this purpose I use a dummy graphic of size 1 x 1. When I’m done calculating, I scrap the graphic and generate a graphic of proper size.

An interesting point in the source code is the Graphics object. What do I need that for when I want to create a bitmap? The reason is that this is my graphics context into which I draw – I can use a graphics context on a screen, a printer and in memory – precisely a Bitmap. The graphics context allows me to perform drawing operations on any device (even when they are virtual).

Using DrawString, I now have output the text “Hello World” according to spec onto a rectangle with white background (created using FillRectangle). The graphic is done. Now I have to save it to disk. Who ever has programmed graphics file formats himself knows how difficult this can be. Not so with GDI+ (Graphics Device Interface) – a simple command is at our disposition:

newBitmap.Save("c:\\test.png", ImageFormat.Png);

And that was it! Simply swap ImageFormat.Png for ImageFormat.Jpeg, and you have a JPEG file. This is just what we have been waiting for – simplest use of graphics.

Now just the exception handling still remains to be explained: some functions can raise exceptions (e.g. not enough memory for the creation of the bitmap etc.). As a good citizen should always clean up after himself, I have to take care of freeing Graphics and Bitmap – and this is exactly what I do in the finally block (because this always is called). And after finally the program ends.

In “theory” the program now works, but only in source code. To get it to run in practice, it has to be compiled first:

csc /R:System.DLL /R:System.Drawing.DLL pagecounter.cs

This gives us the EXE-file pagecounter.exe. Attention: this file can only be executed on systems with the Microsoft .NET framework installed!

Now everything on a web server

As a command line application it ran perfectly. But as an ASP.NET script it should be able to ‘play a few more tricks’:

  • selectable text (e.g. a counter)
  • selectable text color
  • selectable background color
  • selectable font
  • selectable font size

Whoever now says that this is too difficult may now sneak a look the source code of the ASP.NET script for the graphic (pagecounter.aspx). All I had to do was add any amount of error handling code to check the passed parameters for validity. This was the biggest part of necessary changes.

Another necessity was to send the graphic to the client instead of writing it to a file. This new part looks as follows:

MemoryStream tempStream = new MemoryStream();
newBitmap.Save(tempStream,ImageFormat.Png);

Response.ClearContent();
Response.ContentType = "image/png";
Response.BinaryWrite(tempStream.ToArray());
Response.End();

I simply “buffered” the graphic to memory (in a Memory Stream), and then passed it over to the familiar function BinaryWrite as an Array of Bytes . By the way: I need the function ClearContent because in the uppermost part of the Script there are Import Directives which would be sent to the client as blank lines, thus invalidating the PNG graphics file.

Who had a closer look at the source code, will have noticed that I have passed all selectable paramters as querystring parameters. That can become quite a long tail, so being a lazy person, I have constructed a comfortable form for myself so that I can test the various values:

The nice part about the ASP.NET page (pagecountertest.aspx) is that I receive the graphic at once on the same page. The source code for this form already contains a number of server side ASP.NET controls. This is meant to be an appetizer for future articles in which we will deal with form processing (and validation) under ASP.NET in more detail.

prajapat