Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
1018 C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I +
In this example, the FillRectangle() method doesn’t quite fill the entire bitmap. Instead, it leaves a border 1-pixel wide all around. Because you haven’t painted any content to this area, these pixels will have the default color (which, for a bitmap that you render to the GIF format, is black).
The next portion of the drawing code renders a static label message. To do this, you need to create a System.Drawing.Font object that represents the font you want to use. This shouldn’t be confused with the FontInfo object you use with ASP.NET controls to specify the requested font for a web page. Unlike FontInfo, Font represents a single, specific font (including typeface, size, and style) that’s installed on the current computer. When you create a Font object, you specify the font name, point size, and style, as shown here:
Font font = new Font("Impact", 20, FontStyle.Regular);
■Tip Because this image is generated on the server, you can use any font that the server has installed when creating the graphic. The client won’t need to have the same font, because the client receives the text as a rendered image.
To render the text, you use the DrawString() method of the Graphics object. As with the FillRectangle() object, you need to specify the coordinates where the drawing should begin. This point represents the top-left corner of the text block. In this case, the point (10, 5) is used, which gives a distance of 10 pixels from the left and 5 pixels from the top.
g.DrawString("This is a test.", font, Brushes.Blue, 10, 5);
Once the image is complete, you can send it to the browser using the Image.Save() method. Conceptually, you “save” the image to the browser’s response stream. It then gets sent to the client and is displayed in the browser. When you use this technique, your image replaces any other webpage data and bypasses the web control model.
// Render the image to the output stream. image.Save(Response.OutputStream,
System.Drawing.Imaging.ImageFormat.Gif);
■Tip You can save an image to any valid stream, including a FileStream. This technique allows you to save dynamically generated images to disk, so you can use them later in other web pages.
Finally, you should explicitly release your image and graphics context when you’re finished, because both hold onto some unmanaged resources that won’t be released right away if you don’t. You release resources by calling the Dispose() method, as shown here:
g.Dispose();
image.Dispose();
Figure 30-3 shows the completed web page created by this code.
1020 C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I +
To use smoothing in your applications, you set the SmoothingQuality property of the Graphics object. You can choose between None, HighSpeed (the default), AntiAlias, and HighQuality (which is similar to AntiAlias but uses other, slower optimizations that improve the display on LCD screens). The Graphics.SmoothingQuality property is one of the few stateful Graphics class members. This means you set it before you begin drawing, and it applies to any text or shapes
you draw in the rest of the paint session (until the Graphics object is disposed of).
g.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias;
■Tip Antialiasing makes the most difference when you’re displaying curves. That means it will dramatically improve the appearance of ellipses, circles, and arcs, but it won’t make any difference with straight lines, squares, and rectangles.
You can also use antialiasing with fonts to soften jagged edges on text. You can set the Graphics.TextRenderingHint property to ensure optimized text. You can choose between SingleBitPerPixelGridFit (fastest performance and lowest quality), AntiAliasGridFit (better quality but slower performance), and ClearTypeGridFit (the best quality on an LCD display). Or you can use the SystemDefault value to apply whatever font-smoothing settings the user has configured. SystemDefault is the default setting, and the default system settings for most computers enable text antialiasing. Even if you don’t set this, your dynamically rendered text will probably be drawn in high quality. However, because you can’t necessarily control the system settings of the web server, it’s a good practice to specify this setting explicitly if you need to draw text in an image.
The Graphics Class
The majority of the GDI+ drawing smarts is concentrated in the Graphics class. The Graphics class also provides a slew of methods for drawing specific shapes, images, and text. Table 30-1 describes these methods, many of which are used in the examples in this chapter.
Table 30-1. Graphics Class Methods for Drawing
Method |
Description |
DrawArc() |
Draws an arc representing a portion of an ellipse specified by a |
|
pair of coordinates, a width, and a height |
DrawBezier() and |
Draws the infamous and attractive Bezier curve, which is |
DrawBeziers() |
defined by four control points |
DrawClosedCurve() |
Draws a curve and then closes it off by connecting the endpoints |
DrawCurve() |
Draws a curve (technically, a cardinal spline) |
DrawEllipse() |
Draws an ellipse defined by a bounding rectangle specified by a |
|
pair of coordinates, a height, and a width |
DrawIcon() and |
Draws the icon represented by an Icon object and (optionally) |
DrawIconUnstreched() |
stretches it to fit a given rectangle |
DrawImage and |
Draws the image represented by an Image-derived object and |
DrawImageUnscaled() |
(optionally) stretches it to fit a given rectangle |
DrawLine() and DrawLines() |
Draws a line connecting the two points specified by coordinate |
|
pairs |
C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I + |
1021 |
Method |
Description |
DrawPath() |
Draws a GraphicsPath object, which can represent a |
|
combination of curves and shapes |
DrawPie() |
Draws a “piece-of-pie” shape defined by an ellipse specified by a |
|
coordinate pair, a width, a height, and two radial lines |
DrawPolygon() |
Draws a multisided polygon defined by an array of points |
DrawRectangle() and |
Draws an ordinary rectangle specified by a starting coordinate |
DrawRectangles() |
pair and width and height |
DrawString() |
Draws a string of text in a given font |
FillClosedCurve() |
Draws a curve, closes it off by connecting the endpoints, and |
|
fills it |
FillEllipse() |
Fills the interior of an ellipse |
FillPath() |
Fills the shape represented by a GraphicsPath object |
FillPie() |
Fills the interior of a “piece-of-pie” shape |
FillPolygon() |
Fills the interior of a polygon |
FillRectangle() and |
Fills the interior of a rectangle |
FillRectangles() |
|
FillRegion() |
Fills the interior of a Region object |
|
|
The DrawXxx() methods draw outlines (for example, the edge around a rectangle). The FillXxx() methods paint solid regions (for example, the actual surface inside the borders of a rectangle). The only exception is the DrawString() method, which draws filled-in text using a font you specify, and DrawIcon() and DrawImage(), which copy bitmap images onto the drawing surface.
If you want to create a shape that has both an outline in one color and a fill in another color, you need to combine both a draw and a fill method. Here’s an example that first paints a white rectangle and then adds a green border around it:
g.FillRectangle(Brushes.White, 0, 0, 300, 50); g.DrawRectangle(Pens.Green, 0, 0, 299, 49);
■Note If you specify coordinates that are not in the drawing area, you won’t receive an exception. However, the content you draw that’s off the edge won’t appear in the final image. In some cases, this means a partial shape may appear (which might be exactly the effect you want).
You’ll notice that when you use a fill method, you need to specify a Brush object. When you use a draw method, you need to specify a Pen object. In this example, the code uses a prebuilt Pen and Brush object, which can be retrieved from the Pens and Brushes classes, respectively. Brushes retrieved in this way always correspond to solid colors. Pens retrieved in this way are always 1 pixel wide. Later in this chapter (in the “Pens” and “Brushes” sections), you’ll learn how to create your own custom pens and brushes for more exotic patterns.
Using the techniques you’ve learned, it’s easy to create a simple web page that draws a more complex GDI+ image. The next example uses the Graphics class to draw an ellipse, a text message, and an image from a file.
C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I + |
1023 |
Using a GraphicsPath
Two interesting methods that you haven’t seen yet include DrawPath() and FillPath(), which work with the GraphicsPath class in the System.Drawing.Drawing2D namespace.
The GraphicsPath class encapsulates a series of connected lines, curves, and text. To build a GraphicsPath object, you simply create a new instance and use the methods in Table 30-2 to add all the required elements.
GraphicsPath path = new GraphicsPath(); path.AddEllipse(0, 0, 100, 50); path.AddRectangle(New Rectangle(100, 50, 100, 50);
Table 30-2. GraphicsPath Methods
Method |
Description |
AddArc() |
Draws an arc representing a portion of an ellipse specified |
|
by a pair of coordinates, a width, and a height. |
AddBezier() and AddBeziers() |
Draws the infamous and attractive Bezier curve, which is |
|
defined by four control points. |
AddClosedCurve() |
Draws a curve and then closes it off by connecting the |
|
endpoints. |
AddCurve() |
Draws a curve (technically, a cardinal spline). |
AddEllipse() |
Draws an ellipse defined by a bounding rectangle specified |
|
by a pair of coordinates, a height, and a width. |
AddLine() and AddLines() |
Draws a line connecting the two points specified by |
|
coordinate pairs. |
AddPath() |
Adds another GraphicsPath object to this GraphicsPath |
|
object. |
AddPie() |
Draws a “piece-of-pie” shape defined by an ellipse |
|
specified by a coordinate pair, a width, a height, and |
|
two radial lines. |
AddPolygon() |
Draws a multisided polygon defined by an array of points. |
AddRectangle() and AddRectangles() |
Draws an ordinary rectangle specified by a starting |
|
coordinate pair and width and height. |
AddString() |
Draws a string of text in a given font. |
StartFigure() and CloseFigure() |
StartFigure() defines the start of a new closed figure. When |
|
you use CloseFigure(), the starting point will be joined to |
|
the endpoint by an additional line. |
Transform(), Warp(), and Widen() |
Applies a matrix transform, a warp transform (defined |
|
by a rectangle and parallelogram), or an expansion, |
|
respectively. |
|
|
Optionally, you can also create a solid, filled figure from separate line segments. To do this, you first call the StartFigure() method. Then you add the required curves and lines using the appropriate methods. When finished, you call the CloseFigure() method to close off the shape by drawing a line from the endpoint to the starting point. You can use these methods multiple times to add several closed figures to a single GraphicsPath object. Here’s an example that draws a single figure based on an arc and a line:
1024 C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I +
GraphicsPath path = new GraphicsPath(); path.StartFigure();
path.AddArc(10, 10, 100, 100, 20, 50); path.AddLine(20, 100, 70, 230); path.CloseFigure();
Pens
When you use the DrawXxx() methods from the Graphics class, the border of the shape or curve is drawn with the Pen object you supply. You can retrieve a standard pen using one of the static properties from the System.Drawing.Pens class. These pens all have a width of 1 pixel. They differ only in their color.
Pen myPen = Pens.Black;
You can also create a Pen object on your own and configure all the properties described in Table 30-3. Here’s an example:
Pen myPen = new Pen(Color.Red); myPen.DashCap = DashCap.Triangle; myPen.DashStyle = DashDotDot; g.DrawLine(myPen, 0, 0, 10, 0);
Table 30-3. Pen Members
Member |
Description |
DashPattern |
Defines a dash style for broken lines using an array of dashes and |
|
spaces. |
DashStyle |
Defines a dash style for broken lines using the DashStyle enumeration. |
LineJoin |
Defines how overlapping lines in a shape will be joined. |
PenType |
The type of fill that will be used for the line. Typically this will be |
|
SolidColor, but you can also use a gradient, bitmap texture, or hatch |
|
pattern by supplying a brush object when you create the pen. You |
|
cannot set the PenType through this property, however, because it is |
|
read-only. |
StartCap and EndCap |
Determines how the beginning and ends of lines will be rendered. |
|
You can also define a custom line cap by creating a CustomLineCap |
|
object (typically by using a GraphicsPath) and then assigning it to the |
|
CustomStartCap or CustomEndCap property. |
Width |
The pixel width of lines drawn by this pen. |
|
|
The easiest way to understand the different LineCap and DashStyle properties is to create a simple test page that loops through all the options and displays a short line segment of each. The following web-page code creates a drawing that does exactly that:
protected void Page_Load(object sender, System.EventArgs e)
{
//Create the in-memory bitmap where you will draw the image.
//This bitmap is 300 pixels wide and 50 pixels high.
Bitmap image = new Bitmap(500, 400);
Graphics g = Graphics.FromImage(image);
C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I + |
1025 |
//Paint the background. g.FillRectangle(Brushes.White, 0, 0, 500, 400);
//Create a pen to use for all the examples. Pen myPen = new Pen(Color.Blue, 10);
//The y variable tracks the current y (up/down) position
//in the image.
int y = 60;
//Draw an example of each LineCap style in the first column (left). g.DrawString("LineCap Choices", new Font("Tahoma", 15, FontStyle.Bold),
Brushes.Blue, 0, 10);
foreach (LineCap cap in System.Enum.GetValues(typeof(LineCap)))
{
myPen.StartCap = cap; myPen.EndCap = cap; g.DrawLine(myPen, 20, y, 100, y);
g.DrawString(cap.ToString(), new Font("Tahoma", 8), Brushes.Black, 120, y - 10);
y += 30;
}
//Draw an example of each DashStyle in the second column (right).
y = 60;
g.DrawString("DashStyle Choices", new Font("Tahoma", 15, FontStyle.Bold), Brushes.Blue, 200, 10);
foreach (DashStyle dash in System.Enum.GetValues(typeof(DashStyle)))
{
//Configure the pen. myPen.DashStyle = dash;
//Draw a short line segment. g.DrawLine(myPen, 220, y, 300, y);
//Add a text label.
g.DrawString(dash.ToString(), new Font("Tahoma", 8), Brushes.Black, 320, y - 10);
// Move down one line. y += 30;
}
// Render the image to the output stream. image.Save(Response.OutputStream,
System.Drawing.Imaging.ImageFormat.Gif);
g.Dispose();
image.Dispose();
}
Figure 30-6 shows the resulting web page.
1026 C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I +
Figure 30-6. Different pen options
Brushes
Brushes are used to fill the space between lines. Brushes are used when drawing text or when using any of the FillXxx() methods of the Graphics class for painting the inside of a shape.
You can quickly retrieve a predefined solid brush using a static property from the Brushes class, as shown here:
Brush myBrush = Brushes.White;
You can also create a custom brush. You need to decide what type of brush you are creating. Solid brushes are created from the SolidBrush class, and other classes allow fancier options.
HatchBrush: A HatchBrush has a foreground color, a background color, and a hatch style that determines how these colors are combined. Typically, colors are interspersed using stripes, grids, or dots, but you can even select unusual pattern styles such as bricks, confetti, weave, and shingles.
LinearGradientBrush: The LinearGradientBrush allows you to blend two colors in a gradient pattern. You can choose any two colors (as with the hatch brush) and then choose to blend horizontally (from left to right), vertically (from top to bottom), diagonally (from the top-left corner to the bottom-right corner), or diagonally backward (from the top-right corner to the bottom-left corner). You can also specify the origin point for either side of the gradient.
TextureBrush: The TextureBrush attaches a bitmap to a brush. The image is tiled in the painted portion of the brush, whether it is text or a simple rectangle.
C H A P T E R 3 0 ■ DY N A M I C G R A P H I C S A N D G D I + |
1027 |
You can experiment with all these brush types in your applications. Here’s an example of the drawing logic you need to test all the styles of LinearGradientBrush:
protected void Page_Load(object sender, System.EventArgs e)
{
//Create the in-memory bitmap. Bitmap image = new Bitmap(300, 300);
Graphics g = Graphics.FromImage(image);
//Paint the background. g.FillRectangle(Brushes.White, 0, 0, 300, 300);
//Show a rectangle with each type of gradient. LinearGradientBrush myBrush;
int y = 20;
foreach (LinearGradientMode gradientStyle in System.Enum.GetValues(typeof(LinearGradientMode)))
{
//Configure the brush.
myBrush = new LinearGradientBrush(new Rectangle(20, y, 100, 60), Color.Violet, Color.White, gradientStyle);
//Draw a small rectangle and add a text label. g.FillRectangle(myBrush, 20, y, 100, 60); g.DrawString(gradientStyle.ToString(), new Font("Tahoma", 8),
Brushes.Black, 130, y + 20);
//Move to the next line.
y += 70;
}
// Render the image to the output stream. image.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
g.Dispose();
image.Dispose();
}
Figure 30-7 shows the result.
■Tip You can also create a pen that draws using the fill style of a brush. This allows you to draw lines that are filled with gradients and textures. To do so, begin by creating the appropriate brush and then create a new pen. One of the overloaded pen constructor methods accepts a reference to a brush—that’s the one you need to use for a brush-based pen.
