All of the discussion and examples so far have been drawn in C#’s native drawing units: pixels. By default, all of the Graphics object’s filling and drawing methods work in pixels.
However, pixels may not always be the most convenient unit for you. For example, suppose you want to draw a simple bar chart showing sales figures between $1M and $30M for the years 1990 through 2000. Since you can’t use pixel coordinates as large as (1995,1,000,000), you’ll need to perform some mathematical calculations to map these big values into something that will fit on your screen.
While you can perform those calculations yourself if you like, the Graphics object provides transformation methods that can do this for you. The main transformation methods are TranslateTransform, ScaleTransform, and RotateTransform, which translate, scale, and rotate subsequent graphics, respectively.
Basic Transformations
The TranslateTransform method takes as parameters the distances by which graphics should be offset in the X and Y directions; the ScaleTransform method takes parameters that indicate the amount by which graphics should be scaled in the X and Y directions; and RotateTransform indicates the angle in degrees by which graphics should be rotated around the origin.
For example, the following code uses TranslateTransform to translate subsequent graphics 100 pixels in the X direction and 50 pixels in the Y direction. It then draws a rectangle with −50 ≤ X ≤ 50, −50 ≤ Y ≤ 50. After translation, the result appears on the form in the area 50 ≤ X ≤ 150, 0 ≤ Y ≤ 100
e.Graphics.TranslateTransform(100, 50) e.Graphics.DrawRectangle(Pens.Red, -50, -50, 100, 100)
Individually these three methods are fairly simple, but you can combine them to implement very complex transformations. For example, you could scale, rotate, and then translate a drawing.
All three of these methods take an optional final parameter that indicates whether the transformation should be applied before or after any previous transformations. The difference is important because, for example, a rotation followed by a translation generally does not give the same result as a translation followed by a rotation.
By default, transformations are applied before any previous transformations. I find this counterintuitive. Why would I want to have code I add later apply before earlier code? To avoid problems, I always append new transformations after the previous ones.
In addition to these three basic transformation methods, the Graphics class provides several other related methods. One of the most important of these isResetTransform, which resets the object’s transformation to its default non-transformed value. After you finish using a transformation, it’s usually a good idea to reset the transformation so that it doesn’t affect later drawing code, possibly confusing you.
The TransformedSmiley example program uses the following code to draw a smiley face centered at the origin on a Graphics object. Notice that this code uses a pen with width 0. A Pen with width 0 is always drawn as thinly as possible (1 pixel wide), no matter how it is transformed. If you draw with a stock pen such as Pens.Blue, then the result is modified by any scaling transformation and, in this example at least, produces some weird results.
// Draw a smiley face in -1 <= x <= 1, -1 <= y <= 1. private void DrawSmiley(Graphics gr) { using (Pen thin_pen = new Pen(Color.Blue, 0)) { gr.FillEllipse(Brushes.Yellow, -1, -1, 2, 2); gr.DrawEllipse(thin_pen, -1, -1, 2, 2); gr.DrawArc(thin_pen, -0.75f, -0.75f, 1.5f, 1.5f, 0, 180); gr.FillEllipse(Brushes.Red, -0.2f, -0.2f, 0.4f, 0.6F); gr.FillEllipse(Brushes.White, -0.5f, -0.6f, 0.3f, 0.5F); gr.DrawEllipse(thin_pen, -0.5f, -0.6f, 0.3f, 0.5F); gr.FillEllipse(Brushes.Black, -0.4f, -0.5f, 0.2f, 0.3F); gr.FillEllipse(Brushes.White, 0.2f, -0.6f, 0.3f, 0.5F); gr.DrawEllipse(thin_pen, 0.2f, -0.6f, 0.3f, 0.5F); gr.FillEllipse(Brushes.Black, 0.3f, -0.5f, 0.2f, 0.3F); } }
The program uses the following code to draw three copies of the smiley face:
private void Form1_Paint(object sender, PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; // Draw 50x50 pixels. e.Graphics.ScaleTransform(50, 50); e.Graphics.TranslateTransform(60, 60, MatrixOrder.Append); DrawSmiley(e.Graphics); e.Graphics.ResetTransform(); // Draw 50x100 pixels. e.Graphics.ScaleTransform(50, 100); e.Graphics.TranslateTransform(170, 110, MatrixOrder.Append); DrawSmiley(e.Graphics); e.Graphics.ResetTransform(); // Draw 50x30 pixels rotated and flipped vertically. e.Graphics.RotateTransform(45, MatrixOrder.Append); e.Graphics.ScaleTransform(50, -30, MatrixOrder.Append); e.Graphics.TranslateTransform(60, 170, MatrixOrder.Append); DrawSmiley(e.Graphics); e.Graphics.ResetTransform(); }
To draw the first face, the code scales by a factor of 50 in the X and Y directions. This enlarges the original smiley to fill the area −50 ≤ X ≤ 50, −50 ≤ Y ≤ 50. It then translates the result by 60 pixels in the X and Y directions, so the scaled face lies entirely on the form in the area 10 ≤ X ≤ 110,10 ≤ Y ≤ 110.
To draw the second face, the code scales by a factor of 50 in the X direction and 100 in the Y direction to produce a tall, thin face. It then translates to move the result onto the visible part of the form.
To draw the final face, the code rotates the original smiley by 45 degrees. It then scales the result by 50 in the X direction and −30 in the Y direction. Scaling by −30 in the Y direction makes the result 30 times bigger and inverts the Y coordinate so that the result is flipped vertically. Finally, the code translates the result onto the visible part of the form.
Notice that the code calls ResetTransform after drawing each smiley face to reset the transformation. If it did not do this, all of the transformations would add up and produce some very odd results (the second smiley face’s nose is so big it covers the entire form).
Figure 16 shows the program TransformedSmiley displaying its three transformed smiley faces.
World Coordinate Mapping
One particularly common kind of transform maps a rectangle in some sort of world coordinate system to a rectangle on the screen in device coordinates. For example, suppose you want to draw a bar chart showing sales figures between $1M and $30M for the years 1990 through 2000. Drawing will be easier if you can map the world coordinates 1990 ≤ year ≤ 2000, $1M ≤ sales ≤ $30M to the device coordinates that cover the form 0 ≤ X ≤ form width, 0 ≤ Y ≤ form height.
The BarChart example program uses the MapRectangles subroutine shown in the following code to build such a transformation for a Graphics object.
// Transform the Graphics object so // world coordinates wxmin <= X <= wxmax, wymin <= Y <= wymax are mapped to // device coordinates dxmin <= X <= dxmax, dymin <= Y <= dymax. private void MapRectangles(Graphics gr, float wxmin, float wxmax, float wymin, float wymax, float dxmin, float dxmax, float dymin, float dymax) { // Make a world coordinate rectangle. RectangleF wrectf = new RectangleF(wxmin, wymin, wxmax - wxmin, wymax - wymin); // Make PointF objects representing the upper left, upper right, // and lower right corners of device coordinates. PointF[] dpts = { new PointF(dxmin, dymin), new PointF(dxmax, dymin), new PointF(dxmin, dymax) }; // Map the rectangle to the points. gr.Transform = new Matrix(wrectf, dpts); }
It’s not too hard to build a transformation to map one rectangle to another (translate to the original rectangle to the origin, scale, translate to the final destination), but theMatrix class provides a constructor that does this for you. This constructor takes as parameters a source rectangle in world coordinates and an array of three PointFstructures defining the upper-left, upper-right, and lower-left corners of the device rectangle. The code prepares the RectangleF and PointF structures, creates aMatrix, and sets the Graphics object’s Transform property to the result.
The following code shows how the program BarChart uses the subroutine MapRectangles:
// Draw a bar chart filling the form. private void Form1_Paint(object sender, PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; const int MIN_YEAR = 1990; const int MAX_YEAR = 2001; const int MIN_SALES = 0; const int MAX_SALES = 30000000; // Map the coordinates 1990 <= X <= 2001, 0 <= Y <= 30M // to the form's client area, minus a 10 pixel margin. MapRectangles(e.Graphics, MIN_YEAR, MAX_YEAR, MIN_SALES, MAX_SALES, 10, this.ClientSize.Width - 10, this.ClientSize.Height - 10, 10); // Make some data. Point[] sales_data = { new Point(1990, 4000000), new Point(1991, 3000000), new Point(1992, 5000000), new Point(1993, 10000000), new Point(1994, 9000000), new Point(1995, 14000000), new Point(1996, 19000000), new Point(1997, 18000000), new Point(1998, 22000000), new Point(1999, 27000000), new Point(2000, 30000000) }; // Draw the data. using (Pen thin_pen = new Pen(Color.Green, 0)) { // Draw the bars. foreach (Point pt in sales_data) { e.Graphics.FillRectangle(Brushes.PaleGreen, pt.X, 0, 1, pt.Y); e.Graphics.DrawRectangle(thin_pen, pt.X, 0, 1, pt.Y); } // Translate 1/2 year horizontally // so the points lie in the middle of each bar. e.Graphics.TranslateTransform(0.5f, 0f, MatrixOrder.Prepend); thin_pen.Color = Color.Red; e.Graphics.DrawLines(thin_pen, sales_data); } // Draw a box around it all. e.Graphics.ResetTransform(); e.Graphics.DrawRectangle(Pens.Blue, 10, 10, this.ClientSize.Width - 20, this.ClientSize.Height - 20); }
The code first declares some constants to define the world coordinate bounds. It then calls MapRectangles to define a transformation to map the world coordinates to the form’s client area minus a 10-pixel margin. Notice that the minimum Y value in device coordinates is greater than the maximum Y value. Inside the subroutineMapRectangles, this gives the device coordinate rectangle a negative height, and that flips the drawing vertically. The result is that world coordinates are now drawn so that they increase upward as you would normally expect for a bar chart.
After building the transformation, the program defines some data values. It then loops through the values drawing rectangles for each.
Next the code adds a new translation transformation of 0.5 years in the X direction. It prepends the translation to the existing transformation, and thus shifts the data in world coordinates before the other transformations are applied. Finally, the code draws lines connecting the data points.
Figure 17: The BarChart example program uses transformations to draw a bar chart in world coordinates.
Transformations apply to all graphical operations, not just drawing lines and rectangles. The TransformedText example program uses the following code to draw a transformed bitmap and transformed text:
// Draw a transformed bitmap and transformed text.
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
e.Graphics.TranslateTransform(
-Properties.Resources.Smiley.Width / 2,
-Properties.Resources.Smiley.Height / 2);
e.Graphics.ScaleTransform(1.5f, 1f, MatrixOrder.Append);
e.Graphics.RotateTransform(-30, MatrixOrder.Append);
e.Graphics.TranslateTransform(220, 150, MatrixOrder.Append);
e.Graphics.DrawImageUnscaled(Properties.Resources.Smiley, 0, 0);
e.Graphics.ResetTransform();
using (Font the_font = new Font("Times New Roman", 16))
{
e.Graphics.ScaleTransform(1, 4);
e.Graphics.TranslateTransform(10, 10, MatrixOrder.Append);
e.Graphics.DrawString("Tall And Thin", the_font, Brushes.Blue, 0, 0);
e.Graphics.ResetTransform();
e.Graphics.RotateTransform(45);
e.Graphics.TranslateTransform(30, 90, MatrixOrder.Append);
e.Graphics.DrawString("Rotated 45", the_font, Brushes.Blue, 0, 0);
e.Graphics.ResetTransform();
e.Graphics.RotateTransform(-90);
e.Graphics.TranslateTransform(100, 200, MatrixOrder.Append);
e.Graphics.DrawString("Rotated -90", the_font, Brushes.Blue, 0, 0);
e.Graphics.ResetTransform();
e.Graphics.ScaleTransform(4, 1);
e.Graphics.TranslateTransform(140, 10, MatrixOrder.Append);
using (StringFormat sf = new StringFormat ())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Near;
Rectangle rect = new Rectangle(0, 0, 60, 80);
e.Graphics.DrawString("Short And Wide", the_font,
Brushes.Blue, rect, sf);
e.Graphics.DrawRectangle(Pens.Red, rect);
}
e.Graphics.ResetTransform();
}
}
Figure 18 shows the result.
The NameGrid example program, which is shown in Figure 19, performs a more useful task, using transformations to draw a grid with angled names across the top.
Theo 'C# Graphics Programming Wrox Press © 2008'
5 Ways Your Web Design Can Improve Sales
ReplyDeleteDiscover the impact of web design on sales and find practical tips to enhance your website's effectiveness.
https://dalvkotinfotech.com/2024/02/02/boost-sales-with-effective-web-design-strategies/