Before you can create any images or use any graphics commands, you need to decide what you will be drawing to. In a stand-alone application, you will draw to the one of the application's windows. Those of you who have been doing Windows programming for a while now, either in VB or C++, will be familiar with the notion of a device context. A device context is a Windows object that describes a device to which you will draw. The device might be a window, a printer, a disk file, or any other item that is suitable for drawing.
Because your ASP.NET application will not have a window in the classic sense to which it can draw, you will have to create some other object to which you will draw. In this chapter, we will be using bitmaps, and these bitmaps are encapsulated in a Bitmap class. So all of the code samples in this chapter will begin by creating an object to which we will draw that will be a Bitmap object. Actually it would be more correct to refer to this as a surface. This is a similar concept to the DirectX notion, in which all operations in DirectX are done to a surface. So for the rest of this chapter, I won't refer to a device context, but rather I will refer to either a Bitmap or a surface as the destination for all of our drawing operations.
A number of Bitmap object constructors are available to you. But in this chapter I will use the constructor that accepts a width and a height, each as integers, and pixel format argument; and a constructor that accepts a file name and loads an image from a disk file. A pixel format argument is an enumeration that can be seen in Table 10.1.
The follow code fragment creates a Bitmap object that is 300 pixels wide and 200 pixels high. It specifies a format of 24 bits per pixel (BPP) in an RGB color space. I tend to use 24-bit images because most JPEG images are 24 BPP. When I generate dynamic images using 24 BPP, I usually get a more accurate rendering when I compare the created Bitmap object to saved JPEG images.
Bitmap newBitmap = new Bitmap( 300, 200, PixelFormat.Format24bppRgb );VB
Dim newBitmap As New Bitmap(300, 200, PixelFormat.Format32bppRgb)
Once you have created a Bitmap object you then need to obtain a Graphics object that references that Bitmap object. This is similar to getting a device context in Visual Basic or Visual C++ programming. And it is almost identical to what you do in Java. In Java, you have an object named exactly the same thing, Graphics. In Java, you make a method call to getGraphics(), and then you use the Graphics object for all draw operations. In ASP.NET, for a Bitmap object as we have created, you use the static FromImage() method. The FromImage() method takes as an argument that is our Bitmap object to which we want to draw, and returns a Graphics object, which references the Bitmap object. We use this Graphics object then for all our subsequent drawing operations. You can see in the next line how we have obtained a Graphics object from our newly created Bitmap object.C#
Graphics g = Graphics.FromImage( newBitmap );VB
Dim g As Graphics = Graphics.FromImage(newBitmap)
It is time now to create an example application. This example is written in VB, creates a Bitmap object that is 300 pixels wide and 200 pixels high, and draws four rectangles to the Bitmap object. This Bitmap object is then displayed in a browser window.
I need to spend a short time now talking about pens and brushes. You cannot simply select a color, make some sort of call to the graphics API, and have your draw operation be performed in that color. To draw a line or a line-related object in a certain color, you must create a Pen object. Pen objects are created with attributes such as a specified color, a width, and a style (such as dotted, dashed, or other). Then, when you draw a line, you use a Pen object that you have created to let GDI+ know how to draw the line. For instance, if you want to draw a solid red line, you would first create a pen and specify that it would be red and the style would be solid. You would then call the Graphics object's DrawLine() method and specify your newly created line. The following code shows how to create a red pen and then draw a line using this newly created red pen:C#
Pen pen = new Pen( Color.Red ); // Draw a line from the x,y point 10,14 to the point 100,225 g.DrawLine( pen, 10, 14, 100, 225 ); // Or the following if you prefer... g.DrawLine( pen, new Point( 10, 14 ), new Point( 100, 225 ) );VB
Dim pen as new Pen( Color.Red ) ' Draw a line from the x,y point 10,14 to the point 100,225 g.DrawLine( pen, 10, 14, 100, 225 ) ' Or the following if you prefer... g.DrawLine( pen, New Point( 10, 14 ), New Point( 100, 225 ) )
The same idea holds true if you want to draw shapes that are solid in nature, such as a filled rectangle or a filled ellipse. Instead of a Pen, though, you must create a SolidBrush, and the SolidBrush will indicate the color with which you want to draw. The following example shows how to create a SolidBrush and draw a rectangle with it:C#
SolidBrush blueBrush = new SolidBrush( Color.Blue ); // Draw a filled rectangle that starts at the x,y coordinate 0,0 // that is 150 pixels wide and 100 pixels high. g.FillRectangle( blueBrush, 0, 0, 150, 100 );VB
Dim blueBrush as new SolidBrush( Color.Blue ) ' Draw a filled rectangle that starts at the x,y coordinate 0,0 ' that is 150 pixels wide and 100 pixels high. g.FillRectangle( blueBrush, 0, 0, 150, 100 )
And now, let's complete our discussion of creating simple Bitmap objects, obtaining a Graphics object, and then drawing to the Graphics object. Take a look at the application in Figure 10.1. This application draws four rectangles to a created Bitmap object, and then sends the Bitmap object out to the browser or display. The source code for the creation of the Bitmap object can be seen in Listing 10.1.
Listing 10.1 Simple Code That Creates and Draws to a Bitmap Object.
Dim newBitmap As New Bitmap(300, 200, PixelFormat.Format24bppRgb) Dim g As Graphics = Graphics.FromImage(newBitmap) Dim blueBrush As New SolidBrush(Color.Blue) Dim redBrush As New SolidBrush(Color.Red) Dim greenBrush As New SolidBrush(Color.Green) Dim blackBrush As New SolidBrush(Color.Black) g.FillRectangle(blueBrush, 0, 0, 150, 100) g.FillRectangle(redBrush, 150, 0, 150, 100) g.FillRectangle(greenBrush, 0, 100, 150, 100) g.FillRectangle(blackBrush, 150, 100, 150, 100) newBitmap.Save(Response.OutputStream, ImageFormat.Jpeg)
Figure 10.1. This Simple Application Creates a Bitmap, and Then Draws Four Rectangles.
I need to make several comments about the preceding code, and, more specifically, about the call to the Save() method in the last line in Listing 10.1. The first thing you need to notice is that I sent the image to the Response object. The result of this will be to simply send the image bits directly to the browser, without saving or loading them to and from disk. The only thing that the browser will get will be the image bits; it will get no HTML. It will be just as if you loaded an image in your browser outside the context of HTML. I also point out that I used this code inside of the Webform class's Page_Load() method. If I had created a method such as a DrawBitmap() method, and then called it somewhere within the HTML creation process, it would have output what looked like garbage into the HTML stream, as shown in Figure 10.2.
Figure 10.2. Here the Image Was Output to Response.OutputStream Midway into the HTML Rendering Process.
The code in Listing 10.1 could be simplified somewhat. The following two lines create a blue brush and draw a rectangle:C#
SolidBrush blueBrush = new SolidBrush( Color.Blue ); g.FillRectangle( blueBrush, 0, 0, 150, 100 );VB
Dim blueBrush As New SolidBrush(Color.Blue) g.FillRectangle(blueBrush, 0, 0, 150, 100)
A shorter (and possibly more understandable) way to write equivalent code is this:C#
g.FillRectangle( new SolidBrush( Color.Blue ), 0, 0, 150, 100 );VB
g.FillRectangle(New SolidBrush(Color.Blue), 0, 0, 150, 100)
This abbreviation works well if you're only going to use the blue brush once. If, however, you're going to use it more than once, there's no reason to continually create blue brushes with the new operator because doing so will put more pressure on the server's CPU and memory resources.
RECOMMENDED PRACTICE: If you intend to use a GDI+ object more than once, the following code is not efficient because a new brush is created for each FillRectangle() method:C#
g.FillRectangle( new SolidBrush( Color.Blue ), 0, 0, 150, 100 ); g.FillRectangle( new SolidBrush( Color.Blue ), 101, 0, 150, 100 ); g.FillRectangle( new SolidBrush( Color.Blue ), 201, 0, 150, 100 ); g.FillRectangle( new SolidBrush( Color.Blue ), 301, 0, 150, 100 );VB
g.FillRectangle(New SolidBrush(Color.Blue), 0, 0, 150, 100) g.FillRectangle(New SolidBrush(Color.Blue), 101, 0, 150, 200) g.FillRectangle(New SolidBrush(Color.Blue), 201, 0, 150, 300) g.FillRectangle(New SolidBrush(Color.Blue), 301, 0, 150, 400)
Consider changing the code so that a blue brush is created only once, as follows:C#
SolidBrush blueBrush = new SolidBrush( Color.Blue ); g.FillRectangle( blueBrush, 0, 0, 150, 100 ); g.FillRectangle( blueBrush, 101, 0, 150, 100 ); g.FillRectangle( blueBrush , 201, 0, 150, 100 ); g.FillRectangle( blueBrush, 301, 0, 150, 100 );VB
Dim blueBrush As New SolidBrush(Color.Blue) g.FillRectangle(blueBrush, 0, 0, 150, 100) g.FillRectangle(blueBrush, 101, 0, 150, 200) g.FillRectangle(blueBrush, 201, 0, 150, 300) g.FillRectangle(blueBrush, 301, 0, 150, 400)
On a 700MHz machine, the first code was executed 10,000 times in 4.3696 seconds, while the second code was executed 10,000 times in 3.785 seconds.
Another thing I want to point out is the image-format enumerator that was used in the Save() method. The image-format enumerator gives you the capability to save your image in a variety of formats, including BMP, GIF, JPEG, PNG, and TIFF, among others. Table 10.2 lists the formats that are available to you in the image format enumerator.
RECOMMENDED PRACTICE: You must be careful when you choose an image format for your save operations. Different formats will give you different results. For instance, JPEG files are great for photographic images, but they are not very good when your images are sharp and crisp. If your images are sharp and crisp and you save in the JPEG format, you will notice unexplainable artifacts that make the image look less than perfect. GIF images are very good when the desired rendering must be sharp and crisp. But GIF is limited to colors with an 8-bit pixel depth, which limits you to a total of 256 colors in a given image. What that means is that if you have drawn to a Bitmap object in a color that is not available in a saved GIF image, those colors will be mapped in a way that may be somewhat undesirable. For instance, if you draw a color in a shade of green that cannot be found in the standard GIF file-format palette, then your green color will be dithered in a way that most closely approximates the color with which you drew. But it won't look like what you had originally drawn or had expected it to look.
So if your image is counting on some specific colors that are not a part of the standard GIF palette, then you need to consider using JPEG. Alternatively, if your image needs to be crisp and sharp, then you must select GIF. The decision is not always an easy one because you might want some of the benefits that each format has to offer.
As you can see from reading this section, creating and saving images dynamically is an easy process with the .NET framework. ASP.NET applications can easily use the base classes to create images that will enhance the user's experience.