[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Test graphical transformations in C#

[Test graphical transformations in C#]

This example draws several pictures to let you test graphical transformations. The general approach is the same for each picture. The following section explains how the program draws the pictures that demonstrate the various graphical transformations. The sections after that one explain the graphical transformations themselves.

Drawing

The program uses the same method to draw each of the pictures. Here I'll explain how the program draws the picture that demonstrates translation.

When you change the value in a text box, a TextChanged event handler similar to the following executes.

private void txtTranslate_TextChanged(object sender, EventArgs e) { picTranslate.Refresh(); }

This event handler simply refreshes the appropriate PictureBox to make its Paint event handler execute. The following code shows the event handler for the translation picture.

private void picTranslate_Paint(object sender, PaintEventArgs e) { float dx, dy; float.TryParse(txtDx.Text, out dx); float.TryParse(txtDy.Text, out dy); e.Graphics.TranslateTransform(dx, dy); DrawPicture(sender as PictureBox, e.Graphics); }

This code reads the values that you entered in the text boxes to get the parameters that are appropriate for the current transformation. For the translation picture, the code gets the Dy and Dy values.

Next the code applies a transformation to the event handler's Graphics object. It then calls the following DrawPicture method.

private void DrawPicture(PictureBox pic, Graphics gr) { gr.Clear(pic.BackColor); gr.SmoothingMode = SmoothingMode.AntiAlias; DrawSmiley(gr, pic.ClientRectangle); }

This method clears the current PictureBox. It sets the Graphics object's SmoothingMode property to produce smooth lines and curves, and then calls the DrawSmiley method.

The DrawSmiley method just uses a series of drawing methods to draw ellipses, arcs, and other shapes to produce the smiley face. The details aren't important for the discussion of graphical transformations, so I won't show that method here. Download the example to see the details.

There are two important things that you need to know about the DrawSmiley method.

First, the smiley face is drawn with the origin (0, 0) in the upper left corner. Remember that Y coordinates increase downward in .NET graphics.

Second, notice that the smiley face uses some fairly thick pens. The transformations apply to pens so, depending on the transformation, the curves' thickness may vary. For example, if you look closely at the Scale picture, you'll see that the curves are thicker in the X direction than in the Y direction. That's because the scale transformation is larger in the X direction than in the Y direction.

Drawing the Original

The following code shows how the program draws the original smiley face in the upper left PictureBox.

private void picOriginal_Paint(object sender, PaintEventArgs e) { DrawPicture(sender as PictureBox, e.Graphics); }

This code simply calls the DrawPicture method described earlier.

Translating

You've already see this one.

private void picTranslate_Paint(object sender, PaintEventArgs e) { float dx, dy; float.TryParse(txtDx.Text, out dx); float.TryParse(txtDy.Text, out dy); e.Graphics.TranslateTransform(dx, dy); DrawPicture(sender as PictureBox, e.Graphics); }

To apply a translation transformation, call the Graphic object's TranslateTransform method passing it the X and Y offsets that you want to use.

Rotating

The following code shows how to rotate a drawing around the origin.

private void picRotate_Paint(object sender, PaintEventArgs e) { float angle; float.TryParse(txtRotate.Text, out angle); e.Graphics.RotateTransform(angle); DrawPicture(sender as PictureBox, e.Graphics); }

To rotate, call the Graphic object's RotateTransform method passing it the angle through which you want the drawing rotated. The angle is measured clockwise in degrees.

Scaling

The following code shows how to scale a drawing in the X and Y directions.

private void picScale_Paint(object sender, PaintEventArgs e) { float sx, sy; float.TryParse(txtSx.Text, out sx); float.TryParse(txtSy.Text, out sy); try { e.Graphics.ScaleTransform(sx, sy); DrawPicture(sender as PictureBox, e.Graphics); } catch { } }

To scale, call the Graphic object's ScaleTransform method passing it the X and Y scale factors.

This code uses a try catch block because the ScaleTransform method throws an exception if either of the scale factors is zero.

Note that you can use negative scale factors. For example, you could set sx = -1 to flip the drawing horizontally. In this example, that would move the drawing to the left off of the viewable area, so you would probably want to translate it so it is visible again.

Skewing

A skew or shear transformation modifies a pixel's X coordinate by an amount that depends on its Y coordinate and vice versa. For example, suppose the X skew factor is 2. Then the point (a, b) is mapped to the new point (2 * a * y, b).

it's not obvious exactly how general skew transformations work or why you might want to use one. You can use them to do things like rotate an image (but the RotateTransform method is easier) or perform certain kinds of projections, but they're generally relatively confusing.

The following code shows how the program demonstrates the skew transformation.

private void picSkew_Paint(object sender, PaintEventArgs e) { float sx, sy; float.TryParse(txtSkewX.Text, out sx); float.TryParse(txtSkewY.Text, out sy); Matrix matrix = new Matrix(); matrix.Shear(sx, sy); e.Graphics.Transform = matrix; DrawPicture(sender as PictureBox, e.Graphics); }

The Graphics object does not have a skewing method, probably because Microsoft didn't think people would use it very often. (I can't disagree.)

To use a skewing transformation, create a Matrix object and calls its Shear method, passing it the X and Y skew factors. Then set the Graphics object's Transform property equal to that matrix.

The Matrix class provides several other extremely useful methods for performing graphical transformations. The following list summarizes some of the most useful.

  • Rotate - Rotates the drawing around the origin
  • RotateAt - Rotates the drawing around any point, not just the origin
  • Scale - Scales in the X and Y directions
  • Shear - Applies a skew or shear transformation
  • Translate - Translates in the X and Y directions

If you only want to use one or two graphical transformations, the Graphics object's methods are usually easier, but sometimes the Matrix class can be helpful.

Using General Transformations

The Matrix class also lets you set some, but not all, of its elements directly. One of its constructors allows you to pass six floating point values in to assign values to the matrix's left two columns.

If you enter values into the Elements example's text boxes, the program uses the following code to place those values in a Matrix and then use the result to transform the drawing.

private void picElements_Paint(object sender, PaintEventArgs e) { float sx = 1; float sky = 0; float skx = 0; float sy = 1; float dx = 0; float dy = 0; float.TryParse(txtM11.Text, out sx); float.TryParse(txtM12.Text, out sky); float.TryParse(txtM21.Text, out skx); float.TryParse(txtM22.Text, out sy); float.TryParse(txtMdx.Text, out dx); float.TryParse(txtMdy.Text, out dy); try { Matrix matrix = new Matrix(sx, sky, skx, sy, dx, dy); e.Graphics.Transform = matrix; DrawPicture(sender as PictureBox, e.Graphics); } catch { } }

This code simply parses the values that you entered and then passes them into the Matrix class's constructor.

[Test graphical transformations in C#]

The constructor's parameter have the non-intuitive names m11, m12, m21, m22, dx, and dy. The picture on the right shows where those values go in the matrix.

The program saves the values in variables that have slightly more meaningful names. The following list describes the effects those variables have on the transformation.

  • sx - X scale factor
  • sy - Y scale factor
  • skx - X skew factor
  • sky - Y skew factor
  • dx - X translation
  • dy - Y translation

You can use the scale values by themselves relatively easily. Similarly if you use only the skew values, the result is easy to understand. (Or as easy as skewing ever is.)

The translation values move the resulting drawing after any scaling or skewing has happened, so they are also easy to understand, even if you scale or skew the drawing.

[Test graphical transformations in C#]

However, the scale and skew values together interact in non-obvious ways. For example, a rotation uses a specific combination of scale and skew values. The picture on the right shows values that cause rotation by 15 degrees followed by translation of 20 pixels in the X direction and 10 pixels in the Y direction.

You can experiment with the array elements if you like.

As far as I know, there is no way to assign values to a Matrix object's rightmost column. They are assumed to have the values 0, 0, and 1 to make using these objects as transformations easier. That also means you cannot use this class to represent a mathematical matrix, only the kind of matrix used by transformations.

The Matrix class has some other constructors that let you map a source rectangle to a destination parallelogram. It's often easier to use those constructors to perform a complex transformation instead of using a combination of scalings, rotations, and translations.

Combining Transformations

Whenever you call one of the Graphics class's graphical transformation methods, the transformation is added to any previously applied transformation. For example, you could use multiple transformation methods to translate an object, rotate it, and then scale the result.

Note that the result of multiple transformations depends on the order in which you perform them. In general, a rotation followed by a translation is not the same as a translation followed by a rotation.

Each of the Graphics class's graphical transformation methods takes an optional parameter that indicates whether you want the new transformation applied before or after any previously applied transformations. For example, the following statement adds a rotation after any previously applied transformations.

e.Graphics.RotateTransform(45, MatrixOrder.Append);

It is important to note that the default is MatrixOrder.Prepend! That means if you omit this parameter, then the new transformation is applied before the existing transformations. That sort of makes sense from a mathematical point of view and there are certain kinds of programs where that's reasonable, but it's counterintuitive and a common source of bugs in graphics programs.

The Graphics class has three other methods that you may find useful when working with transformations: Save, ResetTransform, and Restore.

The Save method returns a GraphicsState object that represents the Graphics object's current state including its transformations. The Reset method resets the Graphics object and removes any transformations. The Restore method restores a Graphics object to the state it had when you called Save.

For example, suppose your program draws several objects with different transformations. To draw an object you could (1) save the current state, (2) reset the Graphics object and apply transformations for the object, and finally (3) restore the original state. That way you don't need to worry about messing up any previous transformations when you draw the new object.

Drawing Untransformed Lines

As I mentioned earlier, transformations apply to pens. If you scale a drawing in the X and Y directions, the thickness of drawn lines and curves will also be scaled. There are a few ways you can handle this.

First, you can just ignore it and get on with life. This is a good option because it's easy.

Second, a pen with zero width is not scaled. That includes the standard pens such as Pens.Red and Pens.Chartreuse. A pen with zero width is always drawn one pixel wide.

Sometimes, however, you may want to draw a thick line that is not scaled. For example, suppose you are drawing a graph. You could use graphics transformations to make the graph fit the drawwing area nicely, but you then want to draw some thick lines. You can use the Matrix class to do that.

Create a Matrix object and use its methods to define the transformation that you want to use. Like the Graphics class, the Matrix class's graphical transformation methods include an optional parameter that lets you determine the order in which they are applied. (You can also multiply Matrix objects together to combine their transformations if that is more convenient.)

Now you can set the Graphics object's Transform property equal to the Matrix.

To draw a shape with an untransformed pen, use the Matrix object's TransformPoints method to transform the points that define the shape. Then reset the Graphics object and draw the shape using the transformed points. Now the Graphics object uses the identity transformation to draw the transformed points so the pen is not distorted.

Before I finish, I'll tell you one more handy technique that uses the transformation Matrix. You can copy the matrix into a new Matrix object and then call the new object's Invert method. Mathematrically that inverts the matrix. Geometrically it reverses the graphical transformations that the Matrix represents. If the original Matrix represents translation by <-2, 4> followed by a rotation through 30 degrees, then the inverse represents rotation by -30 degrees followed by translation by <2, -4>. In other words, if you apply the first matrix followed by its inverse, then you leave points unchanged.

What this also means is that the inverted matrix can translate from points on the final drawing back to the original coordinate system where you drew the picture.

For a concrete example, suppose you use scaling and translation to make a graph fit nicely on a PictureBox. For example, suppose the graph's X and Y coordinates lie in the ranges 2000 ≤ x ≤ 2020, 0 ≤ y ≤ 100. By using a transformation, you can draw in those coordinates and have the result map to a particular area on the PictureBox.

Now if the user clicks on a point on the PictureBox, you can use the inverted transformation matrix to map that point back to the graph's coordinate system. The PictureBox gives you a point on its surface, and the inverted transformation maps that point back to the graph's coordinates 2000 ≤ x ≤ 2020, 0 ≤ y ≤ 100. Now you can easily tell which part of the graph the user clicked, and you can display a tooltip or display other relevant data.

Conclusion

This isn't the end of the story of transformations. You can read more about them in my book WPF 3d, Three-Dimensional Graphics with WPF and C#. As the title implies, that book focuses on three-dimensional graphics, but transformation concepts apply in two-, three-, or even higher-dimensional graphics. (Although higher-dimensional graphics is pretty esoteric.)

Download the example to experiment with it and to see additional details.

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.