Inverting Transformations
When you need to fit a picture to a printed page, the transformation functions described in the previous section are just about all you need. You create the proper transformation, draw on the paper, and you're done.
When you transform a drawing on the screen, however, you sometimes need to reverse the transformation. For example, suppose you want to let the user interact with a bar chart or map that you've drawn on the screen. If you use transformations to scale the image to fit the form, then the point where the user clicks doesn't necessarily match up with the original drawing coordinates. In other words, the user's click is in device coordinates but the image data is in world coordinates.
To draw the picture, you build a transformation that maps from world coordinates to device coordinates. To see where the user clicked, you need a transformation that maps back from device coordinates to world coordinates. Fortunately each of the transformations used in this article have simple inverses: the inverse of a translation by (
td,
ty) is a translation by (
-tx,
-ty); the inverse of a scaling by (
sx,
sy) is a scaling by (
1/sx,
1/sy); and the inverse of a rotation by
theta degrees is a rotation by
theta degrees.
 | |
Figure 7. Translating from Device Coordinates to World Coordinates: The example program 'BullsEye" maps user mouse clicks from device coordinates to world coordinates. |
To invert a series of transformations, you just apply the inverse transformations in reverse order. For example, the inverse of
TranslateTransform(10, 20),
ScaleTransform(-2, 4),
RotateTransform(30) is
RotateTransform(-30),
ScaleTransform(-1/2, 1/4),
TranslateTransform(-10, -20).
While you could build this transformation yourself easily enough, the Matrix class provides an
Invert method that makes the process even easier. Simply create a Matrix object that represents the Graphics object's transformation and call its
Invert method. Then you can use the inverted Matrix's
TransformPoints method to map points back from device coordinates to world coordinates.
The example program
BullsEye, shown in
Figure 7, uses transformations to draw the same bull's-eye in three differently shaped PictureBoxes. When the user clicks on a picture, the program uses an inverted transformation to find the point that the user clicked in world coordinates. It then tells the user which region was clicked in the form's caption.
When BullsEye starts, it builds the transformations it will need later to map the bull's-eye drawing onto the PictureBoxes. It also inverts those transformations and saves the results so it can map user clicks to world coordinates.
The following code shows how the program builds the transformations for the first PictureBox. The code for the other PictureBoxes is similar so I've omitted it to save space:
Private m_WtoD1 As Matrix ' World to device
Private m_DtoW1 As Matrix ' Device to world
' Make transforms.
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim world_rect As New RectangleF(-1, -1, 2, 2)
Dim device_rect As RectangleF
' PictureBox1.
device_rect = PictureBox1.ClientRectangle
device_rect.Width -= 1
device_rect.Height -= 1
m_WtoD1 = StretchToFitWorld(world_rect, device_rect)
m_DtoW1 = m_WtoD1.Clone
m_DtoW1.Invert()
'...
End Sub
This code builds rectangles to represent the world and device coordinates (it subtracts a bit from the PictureBox's client rectangle so the picture doesn't get covered by the control's border at the edges) and calls the
StretchToFitWorld method to make a transformation that stretches the world coordinates to fit the PictureBox.
The code then uses the transformation matrix's
Clone method to make a copy of the matrix and calls the copy's
Invert method to make the inverse transformation.
The following code shows how the program responds to mouse clicks on PictureBox1:
Private Sub PictureBox1_MouseClick(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles PictureBox1.MouseClick
' Draw the point.
Using gr As Graphics = PictureBox1.CreateGraphics()
gr.FillEllipse(Brushes.Blue, e.X - 2, e.Y - 2, 5, 5)
End Using
' Get the point in device coordinates.
Dim ptf() As PointF = {New PointF(e.X, e.Y)}
' Translate into world coordinates.
m_DtoW1.TransformPoints(ptf)
' See where the user clicked.
Dim dist As Single = ptf(0).X * ptf(0).X + ptf(0).Y * ptf(0).Y
Select Case dist
Case Is < 0.25 * 0.25
Me.Text = "Bulls eye!"
Case Is < 0.5 * 0.5
Me.Text = "50 points"
Case Is < 0.75 * 0.75
Me.Text = "20 points"
Case Is < 1 * 1
Me.Text = "10 points"
Case Else
Me.Text = "Miss"
End Select
End Sub
The code starts by marking the point that the user clicked. It then makes an array of PointF objects containing that point. It uses the inverse transformation matrix
m_DtoW1 to transform the point from device coordinates back into world coordinates. Finally the code checks the point's distance from the origin to see how far the point is from the bull's-eye in world coordinates and displays an appropriate message in the form's title bar.
Transformations let you change the way the Graphics object's drawing methods work, translating, scaling, and rotating the results without changing the drawing code. That lets you make adjustments and reuse code by executing the same drawing code with different transformations. It lets you easily center a picture, possibly while enlarging it with or without stretching.
Most importantly, transformations let you draw in world coordinates that make sense for your application without worrying about how the final result will be mapped onto the device coordinates used by the screen. Inverse transformations let you translate device coordinates back into world coordinates if you need to let users interact with the drawing.
Taken together, these techniques add an entirely new element to your graphics programming that simple Pens and Brushes don't provide on their own.