Make compound lines in WPF and C#

[compound lines]

In order to draw an outlined path, the post Draw an outlined path in WPF and C# explained how to find the left and right edge points for a path. This post explains how to use those points to draw compound lines.


[compound lines]
To see how this works, consider Figure 1. The red dots are left points and the green dots are right points. Now suppose you want to draw a blue segment that is 75% of the way through the original segment (shaded in gray). You simply find new points (blue) that are 75% of the way from the left points (red) to the right points (green) as shown in the figure. Connect the points with a new segment of the desired thickness and you’re done.

The stripes in a compound segment are actually specified as stripe widths. For instance, the example program draws its blue, yellow, and green stripes so they are 20%, 10%, and 70% of the original segment’s width respectively. (Look again at the picture at the top of the post.)

The following GetFigureStripes method converts a figure into a list of Shape objects that represent a figure’s stripes.

// Get Shapes representing a PathFigure's stripes.
// To cover the whole line and nothing more,
// Sum(thicknesses) should equal 1.
private List<Shape> GetFigureStripes(PathFigure figure,
    double thickness, double[] thicknesses, Brush[] brushes)
{
    List<Shape> shapes = new List<Shape>();

    // Get the figure's left and right edge points.
    List<Point> lpoints, rpoints;
    GetFigureLRPoints(figure, thickness,
        out lpoints, out rpoints);

    // Create the stripe shapes.
    double start = 0;
    for (int i = 0; i < thicknesses.Length; i++)
    {
        // Get points for a stripe with width
        // thickness * thicknesses[i] with edge at start.
        double stripe_width = thicknesses[i];
        double half = stripe_width / 2;
        List<Point> points = new List<Point>();
        for (int node = 0; node < lpoints.Count; node++)
        {
            double x1 = lpoints[node].X;
            double y1 = lpoints[node].Y;
            double x2 = rpoints[node].X;
            double y2 = rpoints[node].Y;
            double dx = x2 - x1;
            double dy = y2 - y1;
            Point point = new Point(
                x1 + dx * (start + half),
                y1 + dy * (start + half));
            points.Add(point);
        }

        // Convert the points into a Shape.
        if (figure.IsClosed)
        {
            Polygon polygon = new Polygon();
            polygon.Points = new PointCollection(points);
            polygon.StrokeThickness = stripe_width * thickness;
            polygon.Stroke = brushes[i];
            shapes.Add(polygon);
        }
        else
        {
            Polyline polyline = new Polyline();
            polyline.Points = new PointCollection(points);
            polyline.StrokeThickness = stripe_width * thickness;
            polyline.Stroke = brushes[i];
            shapes.Add(polyline);
        }

        start += stripe_width;
    }

    return shapes;
}

The method takes the following parameters:

  • figure – The PathFigure object that will be striped.
  • thickness – The thickness of the original figure.
  • thicknesses – An array if double giving the thicknesses the stripes should have. To cover the original figure exactly, the sum of the thicknesses should be 1.
  • brushes – The brushes that should be used for the stripes.

Depending on whether the original figure is closed, the method will represent the stripes with either Polygon or Polyline objects. Both of those classes inherit from Shape so the method returns a List<Shape> holding those objects.

The code starts by creating a List<Shape> to hold the results. It then calls the GetFigureLRPoints method to get lists of the figure’s left and right points (the red and green points in Figure 1).

The code then sets variable start to 0. This will keep track of the left edge of the next stripe.

The method then loops through the thicknesses and brushes values to make the stripes.

Inside the loop, variable stripe_width represents the width of the next stripe. This value should simply be the next value in the thicknesses array. If you give each stripe exactly its desired thickness, however, the stripes won’t fit together perfectly so small gaps may appear between them.



[compound lines]
To fix that, the code expands the stripe so it overlaps the following stripe by half of the following stripe’s width. The bottom of Figure 2 shows the stripes overlapping. The top of Figure 2 shows the result when the stripes are drawn.

To make the overlap, the code adds half of the following stripe’s width to stripe_width. It then calculates half of the resulting width so it can center the new stripe over its midpoint at the position start + half.

The code then loops through the left and right edge points. For each pair of points, it calculates the difference values dx and dy. It then adds a point to the stripe that is the appropriate distance between the left and right edge points.

Remember that during the calculation the measurements such as start and half are fractions of the total 1. For example, suppose a stripe should have width 0.4 (including the overlap with the following stripe) and that it should begin 0.3 of the way through the original figure. Then start will be 0.3 and half will be 0.2, so the next point on the stripe will have coordinates:

    (x1 + (0.3 + 0.2) * dx, y1 + (0.3 + 0.2) * dy)

After it has generated the stripe’s points, the code checks whether the original figure was closed and uses the stripe’s points to make either a Polygon or a Polyline. It sets the object’s thickness and brush, and adds the result to the shapes list.

After it finishes the stripe, the code adds the stripe’s thickness (without the overlap) to start so the next stripe begins at the appropriate position. It then repeats the loop to make the next stripe.

Simple, right? Well, maybe not all that simple, but hopefully you get the idea. There are still plenty of small details in the code that finds the left and right edge points. Even though the complete code is pretty long, I don’t think the pieces are too confusing. Download the example program to see all of the details.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in algorithms, drawing, graphics, mathematics and tagged , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *