Reader Level:
ARTICLE

Two-dimensional computer graphics or vector graphics in Silverlight for Windows Phone 7

Posted by Charles Petzold Articles | Windows Phone December 20, 2010
Vector graphics is the visual realization of analytic geometry. Two-dimensional coordinate points in the form (x, y) define straight lines and curves. In Silverlight, these curves can be arcs on the circumference of an ellipse or Bezier curves, either in the customary cubic form or in a simplified quadratic form.
  • 0
  • 0
  • 6886
Download Files:
 

This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html

Vector graphics is the visual realization of analytic geometry. Two-dimensional coordinate points in the form (x, y) define straight lines and curves. In Silverlight, these curves can be arcs on the circumference of an ellipse or Bezier curves, either in the customary cubic form or in a simplified quadratic form. You can "stroke" these lines with a pen of a desired brush, width, and style. A series of connected lines and curves can also define an enclosed area that can be filled with a brush.

A Silverlight program that needs to draw vector graphics uses classes defined in the System.Windows.Shapes namespace, commonly referred to as the Shapes library. This namespace consists of an abstract class named Shape and six sealed classes that derive from Shape.

Canvas and Grid

The Line class defines four properties of type double named X1, Y1, X2, and Y2. The line is drawn from the point (X1, Y1) to the point (X2, Y2) relative to its parent:

1.gif

Although the Canvas panel seems like a natural for vector graphics, you'll get the same image if you use a single-cell Grid:

2.gif

Normally when you use a Canvas you use the Canvas.Left and Canvas.Top attached properties to position elements within the Canvas. Those properties are not required with the Line because it has its own coordinates. You could use the attached properties with the Line but the values are compounded with the coordinates:

3.gif

Overlapping and ZIndex

Here are two lines:

4.gif

The second one overlaps the first one. You can see that more clearly if you go beyond the default 1-pixel thickness of the line using StrokeThickness:

5.gif

If you would prefer that the blue line be on top of the red line, there are two ways you can do it. You could simply swap the order of the two lines in the Grid:

6.gif

Or, you could set the Canvas.ZIndex property. Although this property is defined by Canvas it works with any type of panel:

7.gif

Polylines and Custom Curves

The Line element looks simple but the markup is a little bloated. You can actually reduce the markup for drawing a single line by switching from the Line to the Polyline:

8.gif

Now let's suppose you want to use Polyline to draw a circle. Commonly, a circle centered at the point (0, 0) with a radius R is defined as all points (x, y) that satisfy the equation:

X2 + Y2  = R2

This is also, of course, the Pythagorean Formula.

Let's create a new projecvt. Bring up the MainPage.cs file and install a handler for the Loaded event to allow accessing the dimensions of the ContentPanel grid. Here are calculations for center and radius for a circle to occupy the center of a content panel and reach to its edges:

    Point center = new Point(ContentPanel.ActualWidth / 2,
                                      ContentPanel.ActualHeight / 2 - 1);
    double radius = Math.Min(center.X - 1, center.Y - 1);

Notice the pixel subtracted from the calculation of the radius. This is to prevent the circle from being geometrically the same as the content area size. The stroke thickness straddles the geometric line so it would otherwise get cropped off at the edges.

Now create a Polyline and set the Stroke and StrokeThickness properties:

    Polyline polyline = new Polyline();
    polyline.Stroke = this.Resources["PhoneForegroundBrush"] as Brush;
    polyline.StrokeThickness = (double)this.Resources["PhoneStrokeThickness"];

Calculate the Point objects in a for loop based on the formulas I've just showed you and add them to the Points collection of the polyline:

    for (double angle = 0; angle < 360; angle += 0.25)
    {
         double radians = Math.PI * angle / 180;
         double x = center.X + radius * Math.Cos(radians);
         double y = center.Y + radius * Math.Sin(radians);
         polyline.Points.Add(new Point(x, y))
    }

Now add the Polyline to the Grid:

    ContentPanel.Children.Add(polyline);

And here's the result:

9.gif

let's do something a little different. Let's make the angle go all the way to 3600:

Here's the complete class:

namespace Spiral
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent();
            Loaded += OnLoaded;
        } 
        void OnLoaded(object sender, RoutedEventArgs args)
        {
            Point center = new Point(ContentPanel.ActualWidth / 2,
                                     ContentPanel.ActualHeight / 2);
            double radius = Math.Min(center.X - 1, center.Y - 1); 
            Polyline polyline = new Polyline();
            polyline.Stroke = this.Resources["PhoneForegroundBrush"] as Brush;
            polyline.StrokeThickness = (double)this.Resources["PhoneStrokeThickness"]; 
            for (double angle = 0; angle < 3600; angle += 0.25)
            {
                double scaledRadius = radius * angle / 3600;
                double radians = Math.PI * angle / 180;
                double x = center.X + scaledRadius * Math.Cos(radians);
                double y = center.Y + scaledRadius * Math.Sin(radians);
                polyline.Points.Add(new Point(x, y));
            }
            ContentPanel.Children.Add(polyline);
        }
    }
}

We use that scaledRadius value for multiplying by the sine and cosine values. Now the result is an Archimedian spiral:

10.gif

Caps, Joins, and Dashes

When you're displaying thick lines, you might want a little different appearance on the ends of the lines. These are known as line caps-"caps" like a hat. The available caps are members of the PenLineCap enumeration: Flat (the default), Square, Round, and Triangle. Set the StrokeStartLineCap property to one of these values for the cap at the beginning of the line, and set StrokeEndLineCap for the cap at the end. Here are Round and Triangle capping off a 30-pixel line:

11.gif

The difference between Flat and Square might not be obvious at first. To better clarify the difference, the following markup displays a thinner line over the thick line with the same coordinates to indicate the geometric start and end of the line:

12.gif

The Flat cap (at the upper left) cuts off the line at the geometric point. The Square extends the line for half the line thickness. My favorite caps are the rounded ones:

13.gif

As you can see, they also extend the rendered size of the line by half the stroke thickness.

You can also specify what happens at the corners. Set the StrokeLineJoin property to a member of the PenLineJoin enumeration. Here's Round:

14.gif

The Miter join has a little built-in problem. If the lines meet at a very sharp angle, the miter can be very long. For example, a 10-pixel wide line that makes an angle of 1 degree will have a miter point over 500 pixels long! To avoid this type of weirdness a StrokeMiterLimit property kicks in for extreme cases:

15.gif

Here are two lines, one thick, one thin overlaying the thick line, with the same geometric points, going from the upper-left to the lower-left:

16.gif

You can make the line dashed by setting the StrokeDashArray, which is generally just two numbers, for example 1 and 1:

17.gif

If you want to draw a dotted line with actual round dots, obviously you want to use the Round dash cap, and you want each dot to be separated by its neighbor by the dot width. The StrokeDashArray required for this job is somewhat non-intuitive. It's a dash length of 0 and a gap length of 2:

18.gif

You can use a dotted line around an ellipse if you want:

19.gif

Polygon and Fill

The Polyline that I've been using to demonstrate dotted lines is only three sides of a square:

20.gif

But if you set the Fill brush, the interior is filled as if the polyline describes a closed area:

21.gif

The NonZero fill rule is a bit more complex because it takes account of the directions that boundary lines are drawn. If the boundary lines drawn in one direction balance out the boundary lines drawn in the opposite direction, then the area is not filled. In any interior area of this star, however, all the boundary lines go in the same direction.

Neither of these two FillRule options guarantees that all interior areas get filled. Here's a rather artificial figure that has an enclosed but unfilled area even with NonZero:

22.gif

The Stretch Property

The only settable property defined by Shape that I haven't discussed yet is Stretch. This is similar to the same property in the Image element; you set it to a member of the Stretch enumeration, either None (the default), Fill, Uniform, or UniformToFill. Here's an innocent little Polygon with its Stretch property set to Fill.

23.gif

COMMENT USING

Trending up