Graphics Containers

Suppose that you have a surface with 100 different graphics objects (text, shapes, and images), and you want to anti-alias just one object, perhaps for performance reasons. Without graphics containers, you would have to create a Graphics object and set the SmoothingMode property to AntiAliaswhich would set anti-aliasing for everything drawn on the object. How do you set the smoothing mode of only one particular object on a surface? That's where containers come in.

The Graphics class provides methods and properties to define the attributes of graphics objects. For example, you can set the rendering quality of text using the TextRenderingHint property. The smoothing mode represents the quality of the graphics objects, the compositing quality represents the quality of composite images, the compositing mode represents whether pixels from a source image overwrite or are combined with background pixels, and the interpolation mode represents how intermediate values between two endpoints are calculated. These attributes are set with the SmoothingMode, CompositingMode, CompositingQuality, and InterpolationMode propertieswhich are applicable for an entire Graphics object. For example, if you set the SmoothingMode property of a Graphics object to AntiAlias, all graphics objects attached to that Graphics object will be anti-aliased.

A graphics container is a temporary graphics object that acts as a canvas for graphics shapes, allowing an application to set a container property separately from the main Graphics object. An application can apply properties to a Graphics object within a container, and these properties won't be available outside of that container. Thus we can selectively apply properties to Graphics objects.

In Figure 9.20, for example, a Graphics object includes three graphics containers, each with different properties. These properties are not available outside of their containers. All graphics objects inside a container may be affected by the container property. It's also possible to have nested containers.

Figure 9.20. Nested containers

Graphics containers do not inherit their parent's settings. In Figure 9.20, for example, the Graphics object is a container whose compositing quality is set to high, and whose smoothing mode is set to high-speed. The graphics containers won't have high-speed and high-quality rendering unless we set them within the container itself. The smoothing mode of graphics container A is set to anti-aliasing; that of graphics container B is set to high quality. Graphics container C is a nested container within graphics container A, with interpolation mode set to high.

Before we discuss graphics containers in more detail, let's take a look at graphics states.

9.3.1 Understanding Graphics States

During the life cycle of a Graphics object, the object maintains a list of graphics states. These graphics states fall into various categories depending on the operations being applied to the Graphics object. For example, setting the compositing quality of a Graphics object changes the object's state.

Graphics states can be divided into three categories:

  1. Quality settings
  2. Transformations
  3. Clipping region

The first state of the Graphics object involves the quality of shapes and images. This state changes when you set the quality of a Graphics object using the SmoothingMode, TextRenderingHint, CompositingMode, CompositingQuality, and InterpolationMode properties of the Graphics class.

Transformation is another state that a Graphics object maintains. Transformation is the process of changing graphics objects from one state to another by rotation, scaling, reflection, translation, and shearing.

The Graphics object maintains two transformation states: world and page. The world transformation defines the conversion of world coordinates to page coordinates. World coordinates are coordinates that you define in your program, and page coordinates are coordinates that GDI+ uses to expose the object coordinates. The page transformation defines the conversion of page coordinates to device coordinates. Device coordinates determine how a graphics object will be displayed on a particular display device.

The Graphics class provides the ScaleTransform, RotateTransform, and TranslateTransform methods, as well as the Transform property, to support transformations.

Note

Chapter 10 discusses transformations and transformation-related classes, methods, and properties in greater detail.

The world unit (by default) is always defined as a pixel. For example, in the following code snippet a rectangle will be drawn starting at 0 pixels from the left edge and 0 pixels from the top edge, with width and height of 100 and 50 pixels, respectively.

 

Graphics g = this.CreateGraphics(); g.DrawRectangle(Pens.Green, 0, 0, 100, 50);

 

Page coordinates may be different from world coordinates, depending on the page unit and page scaling of the Graphics object. For example, if the page unit is an inch, the page coordinates will start at point (0, 0), but the width and height of the rectangle will be 100 inches and 50 inches, respectively.

Table 9.9. GraphicsUnit members

Member

Description

Display

1/75 inch as the unit of measure.

Document

The document unit (1/300 inch) as the unit of measure.

Inch

An inch as the unit of measure.

Millimeter

A millimeter as the unit of measure.

Pixel

A pixel as the unit of measure.

Point

A printer's point (1/72 inch) as the unit of measure.

World

The world unit as the unit of measure.

The PageScale and PageUnit properties define a page transformation. The PageUnit property defines the unit of measure used for page coordinates, and the PageScale property defines the scaling between world and page units for a Graphics object. The PageUnit property takes a value of type GraphicsUnit enumeration, which is defined in Table 9.9.

Listing 9.13 draws three ellipses with the same size but different PageUnit values: Pixel, Millimeter, and Point.

Listing 9.13 Setting page transformation

private void TransformUnits_Click(object sender, System.EventArgs e) { // Create a Graphics object and set its // background as form's background Graphics g = this.CreateGraphics(); g.Clear(this.BackColor); // Draw an ellipse with default units g.DrawEllipse(Pens.Red, 0, 0, 100, 50); // Draw an ellipse with page unit as pixel g.PageUnit = GraphicsUnit.Pixel; g.DrawEllipse(Pens.Red, 0, 0, 100, 50); // Draw an ellipse with page unit as millimeter g.PageUnit = GraphicsUnit.Millimeter; g.DrawEllipse(Pens.Blue, 0, 0, 100, 50); // Draw an ellipse with page unit as point g.PageUnit = GraphicsUnit.Point; g.DrawEllipse(Pens.Green, 0, 0, 100, 50); // Dispose of object g.Dispose(); }

Figure 9.21 shows the output from Listing 9.13. Although the parameters to DrawEllipse are the same, we get results of different sizes because of the different PageUnit settings.

Figure 9.21. Drawing with different PageUnit values

The third state of the Graphics object is the clipping region. A Graphics object maintains a clipping region that applies to all items drawn by that object. You can set the clipping region by calling the SetClip method. It has six overloaded forms, which vary in using a Graphics object, graphics path, region, rectangle, or handle to a GDI region as the first parameter. The second parameter in all six forms is CombineMode, which has six values: Complement, Exclude, Intersect, Replace, Union, and Xor. The Clip property of the Graphics object specifies a Region object that limits the portion of a Graphics object that is currently available for drawing. The ClipBounds property returns a RectangleF structure that represents a bounding rectangle for the clipping region of a Graphics object.

Note

Chapter 6 discussed clipping regions and the CombineMode enumeration in detail.

 

9.3.2 Saving and Restoring Graphics States

The GraphicsState class represents the state of a Graphics object. This class does not have any useful properties or methods, but it is used by the Save and Restore methods of the Graphics object. A call to the Save method saves a GraphicsState object as an information block on the stack and returns it. When this object is passed to the Restore method, the information block is removed from the stack and the graphics state is restored to the saved state.

You can make multiple calls to Save (even nested), and each time a new state will be saved and a new GraphicState object will be returned. When you call Restore, the block will be freed on the basis of the GraphicsState object you pass as a parameter.

Now let's see how this works in our next example. We create a Windows application, add a MainMenu control and its items, and write click event handlers for these items. Listing 9.14 creates and saves graphics states using the Save method, then restores them one by one. The first saved state stores page units and a rotation transformation; the second state stores a translation transformation. We save the first graphics state as gs1. Then we call the TranslateTransform method, which translates and transforms the graphics object. We save the new graphics state as gs2. Now we call ResetTransform, which removes all the transformation effects. Then we draw an ellipse. We restore the graphics states by calling GraphicsState.Restore methods for both gs1 and gs2, and we fill a rectangle and draw an ellipse, respectively.

Listing 9.14 Saving and restoring graphics states

private void SaveRestoreMenu_Click(object sender, System.EventArgs e) { // Create a Graphics object and set its // background as the form's background Graphics g = this.CreateGraphics(); g.Clear(this.BackColor); // Page transformation g.PageUnit = GraphicsUnit.Pixel; // World transformation g.RotateTransform(45, MatrixOrder.Append); // Save first graphics state GraphicsState gs1 = g.Save(); // One more transformation g.TranslateTransform(0, 110); // Save graphics state again GraphicsState gs2 = g.Save(); // Undo all transformation effects by resetting // the transformation g.ResetTransform(); // Draw a simple ellipse with no transformation g.DrawEllipse(Pens.Red, 100, 0, 100, 50); // Restore first graphics state, which means // that the new item should rotate 45 degrees g.Restore(gs1); g.FillRectangle(Brushes.Blue, 100, 0, 100, 50); // Restore second graphics state g.Restore(gs2); g.DrawEllipse(Pens.Green, 100, 50, 100, 50); // Dispose of Graphics object g.Dispose(); }

Figure 9.22 shows the output from Listing 9.14. The first ellipse has no transformation effects, but the rectangle and ellipse below do have transformation effects.

Figure 9.22. Saving and restoring graphics states

9.3.3 Working with Graphics Containers

Graphics containers were introduced earlier in this chapter. Now let's see how to create and use them in our applications.

9.3.3.1 Creating a Graphics Container

The BeginContainer method of the Graphics class creates a container. Each BeginContainer method is paired with an EndContainer method. You can also create nested containers. The following code snippet creates two containers:

 

GraphicsContainer gContrainer1 = g.BeginContainer(); // Do something here GraphicsContainer gContrainer2 = g.BeginContainer(); // Do something here g.EndContainer(gContrainer2); g.EndContainer(gContrainer1);

 

9.3.3.2 Using Graphics Containers to Draw Text

As mentioned earlier, graphics containers are temporary canvases. Let's see how to set the quality of different text for different containers. Listing 9.15 creates two containers, and each has different properties. The first container sets the TextRenderingHint property to AntiAlias and the TextContrast property to 4. The second container sets TextRenderingHint to AntiAliasGridFit and TextContrast to 12. After creating Font and SolidBrush objects, we set the TextRenderingHint property of the Graphics object, and then we call DrawString. Finally, we call EndContainer to terminate the container scope.

Listing 9.15 Using different graphics containers to draw text

private void DrawTextMenu_Click(object sender, System.EventArgs e) { // Create a Graphics object and set its // background as the form's background Graphics g = this.CreateGraphics(); g.Clear(this.BackColor); // Create font and brushes Font tnrFont = new Font("Times New Roman", 40, FontStyle.Bold, GraphicsUnit.Pixel); SolidBrush blueBrush = new SolidBrush(Color.Blue); g.TextRenderingHint = TextRenderingHint.SystemDefault; // First container boundary starts here GraphicsContainer gContrainer1 = g.BeginContainer(); // Gamma correction value 0 - 12. Default is 4. g.TextContrast = 4; g.TextRenderingHint = TextRenderingHint.AntiAlias; g.DrawString("Text String", tnrFont, blueBrush, new PointF(10, 20)); // Second container boundary starts here GraphicsContainer gContrainer2 = g.BeginContainer(); g.TextContrast = 12; g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; g.DrawString("Text String", tnrFont, blueBrush, new PointF(10, 50)); // Second container boundary finishes here g.EndContainer(gContrainer2); // First container boundary finishes here g.EndContainer(gContrainer1); // Draw string outside of the container g.DrawString("Text String", tnrFont, blueBrush, new PointF(10, 80)); // Dispose of Graphics object blueBrush.Dispose(); g.Dispose(); }

Note

The TextRenderingHint enumeration is defined in the System.Drawing.Text namespace. Don't forget to add this namespace reference.

Figure 9.23 shows the output from Listing 9.15. Notice the quality difference in the text.

Figure 9.23. Using graphics containers to draw text

9.3.3.3 Using Graphics Containers to Draw Shapes

In the previous section we saw how we can use containers to draw text with different rendering quality and performance. We can draw other shapes using SmoothingMode, CompositingQuality, and other properties.

Listing 9.16 uses the AntiAlias, GammaCorrected, and HighSpeed options to draw rectangles and ellipses. We create a container by calling BeginContainer, set the smoothing mode to anti-aliasing, and set the compositing quality and gamma correction of the Graphics object. Then we draw an ellipse and a rectangle. After that we create a second graphics container by making another call to BeginContainer and set the smoothing mode and compositing quality to high speed, and then we draw a new ellipse and rectangle. Finally, we make two calls to the EndContainer method to close the containers.

Listing 9.16 Using graphics containers to draw shapes

private void DrawShapesMenu_Click(object sender, System.EventArgs e) { // Create a Graphics object and set its // background as the form's background Graphics g = this.CreateGraphics(); g.Clear(this.BackColor); // Create pens Pen redPen = new Pen(Color.Red, 20); Pen bluePen = new Pen(Color.Blue, 10); // Create first graphics container GraphicsContainer gContainer1 = g.BeginContainer(); // Set its properties g.SmoothingMode = SmoothingMode.AntiAlias; g.CompositingQuality = CompositingQuality.GammaCorrected; // Draw graphics objects g.DrawEllipse(redPen, 10, 10, 100, 50); g.DrawRectangle(bluePen, 210, 0, 100, 100); // Create second graphics container GraphicsContainer gContainer2 = g.BeginContainer(); // Set its properties g.SmoothingMode = SmoothingMode.HighSpeed; g.CompositingQuality = CompositingQuality.HighSpeed; // Draw graphics objects g.DrawEllipse(redPen, 10, 150, 100, 50); g.DrawRectangle(bluePen, 210, 150, 100, 100); // Destroy containers g.EndContainer(gContainer2); g.EndContainer(gContainer1); // Dispose of objects redPen.Dispose(); bluePen.Dispose(); g.Dispose(); }

Figure 9.24 shows the output from Listing 9.16 The first ellipse and rectangle are smoother than the second set.

Figure 9.24. Using graphics containers to draw shapes

Graphics containers are also useful when you need to render large images either with high quality or at high speed. For example, if you have two large images and only one is quality-sensitive, you can create two graphics containers and set high quality for the first container and high speed for the second.

Категории