Drawing shapes with Windows Composition in UWP

This article explains how you can use the Composition API to display and animate geometric shapes in a UWP app. Before the April Windows 10 Update, this API was limited to drawing rectangles that you could fill with a brush or an image. The latest update does more than rectangles: it supports shapes from lines and ellipses to rounded rectangles and even SVG paths. This is good news for developers who want to use more of Windows Composition in their apps, e.g. for drawing controls or for animating content à la Lottie.

In this article I will not dive too much in detail on how to draw and animate Composition sprites in XAML. Please refer to one of my previous blog posts for an introduction to the core concepts. In this article we’ll stick to the new classes that were added to display shapes:

To demonstrate these new classes I built a Clock UserControl that’s made of ellipses and rounded rectangles drawn and animated by Windows Composition. This is how it looks like:
Standard

I didn’t have to start from scratch, I upgraded an existing project that used rectangles and images:
clock_original

Using the new API

The upgraded clock control is a custom user control that has

  • ellipses as background face and hour ticks,
  • rounded rectangles for hour and minute hands, and
  • a simple shape – a triangle- for the seconds hand.

Everything is done with the Composition API, the only XAML is

  • a ViewBox for easy calculations, with
  • an empty Canvas to host the visual:
<Viewbox Stretch="Uniform">
    <Canvas x:Name="Container"
            Background="Transparent"
            Height="200"
            Width="200">
    </Canvas>
</Viewbox>

The ContainerVisual that links the Composition elements to XAML is defined in the Loaded event of the hosting control, since that’s the first event where we have access to the Container canvas. For convenience, I still use an extension method to link the two worlds (maybe the new API has improvements here, which I didn’t notice):

_root = Container.GetVisual();
public static class UIElementExtensions
{
    public static ContainerVisual GetVisual(this UIElement element)
    {
        var hostVisual = ElementCompositionPreview.GetElementVisual(element);
        var root = hostVisual.Compositor.CreateContainerVisual();
        ElementCompositionPreview.SetElementChildVisual(element, root);
        return root;
    }
}

The Compositor is still used as a factory to create the rest of the instances.

Here’s the routine that draws the hour hand as a Win2D geometry and lifts it to the XAML stack – from CompositionRoundedRectangleGeometry to CompositionSpriteShape to ShapeVisual:

var roundedRectangle = _compositor.CreateRoundedRectangleGeometry();
roundedRectangle.Size = new Vector2(6.0f, 63.0f);
roundedRectangle.CornerRadius = new Vector2(3.0f, 3.0f);
_hourhandSpriteShape = _compositor.CreateSpriteShape(roundedRectangle);
_hourhandSpriteShape.FillBrush = _compositor.CreateColorBrush(Colors.DarkSlateGray);
_hourhandSpriteShape.Offset = new Vector2(97.0f, 40.0f);
_hourhandSpriteShape.CenterPoint = new Vector2(3.0f, 60.0f);
handShapeVisual = _compositor.CreateShapeVisual();
handShapeVisual.Size = new Vector2(200.0f, 200.0f);
handShapeVisual.Shapes.Add(_hourhandSpriteShape);
_root.Children.InsertAtTop(handShapeVisual);

The seconds hand is a triangle made out of three lines. Drawing it was a little bit harder than expected. The constructor of CompositionPath requires a IGeometrySource2D instance, which brings you pretty close to the Win2D primitives (and out of my enterprise XAML developer comfort zone). Fortunately I came across this blog post by Daren May where he shares an extension method on CanvasPathBuilder to build a path with lines:

public static class PathBuilderExtensions
{
    public static CanvasPathBuilder BuildPathWithLines(
        this CanvasPathBuilder builder,
        IEnumerable<Vector2> vectors,
        CanvasFigureLoop canvasFigureLoop)
    {
        var first = true;

        foreach (var vector2 in vectors)
        {
            if (first)
            {
                builder.BeginFigure(vector2);
                first = false;
            }
            else
            {
                builder.AddLine(vector2);
            }
        }

        builder.EndFigure(canvasFigureLoop);
        return builder;
    }

    public static CanvasPathBuilder BuildPathWithLines(
        this CanvasPathBuilder builder,
        IEnumerable<(float x, float y)> nodes,
        CanvasFigureLoop canvasFigureLoop)
    {
        var vectors = nodes.Select(n => new Vector2(n.x, n.y));
        return BuildPathWithLines(builder, vectors, canvasFigureLoop);
    }
}

Here’s the routine that draws the seconds hand from three line coordinates into a CompositionSpriteShape:

var canvasPathBuilder = new CanvasPathBuilder(new CanvasDevice());
canvasPathBuilder.BuildPathWithLines(new(float x, float y)[]
    {
        (0, 80),
        (3, 0),
        (6, 80)
    },
    CanvasFigureLoop.Closed);
var canvasGeometry = CanvasGeometry.CreatePath(canvasPathBuilder);
var compositionPath = new CompositionPath(canvasGeometry);
var pathGeometry = _compositor.CreatePathGeometry();
pathGeometry.Path = compositionPath;
_secondhandSpriteShape = _compositor.CreateSpriteShape(pathGeometry);

The –smooth- animations to redraw the hour, minute, and seconds hands act at CompositionObject level –the base class of all visual composition objects-, so there was no need to modify from the original clock here.

The old clock successfully upgraded from straight rectangles to rounded ones, ellipses, and lines. Let’s now get closer to real SVG paths.

Getting some help from CompositionProToolkit

When looking for more advanced extension methods for CanvasPathBuilder, I came across the CompositionProToolkit by Ratish Philip. This is a very rich collection of Win2D based helper classes and controls for Windows Composition. The helper classes allowed me to build a ‘pro’ version of the clock that comes with

  • ellipses for hour ticks and seconds hand,
  • an SVG-ish complex path as background face, and
  • a container shape for the hour and minute hands.

Here’s how that clock looks like:
Pro

Here’s how the seconds hand (just a circular dot) is drawn using one of the many extension methods on CanvasPathBuilder:

var canvasPathBuilder = new CanvasPathBuilder(new CanvasDevice());
canvasPathBuilder.AddCircleFigure(new Vector2(0.0f, 0.0f), 2.0f);
var canvasGeometry = CanvasGeometry.CreatePath(canvasPathBuilder);
var compositionPath = new CompositionPath(canvasGeometry);
var pathGeometry = _compositor.CreatePathGeometry();
pathGeometry.Path = compositionPath;
_secondhandSpriteShape = _compositor.CreateSpriteShape(pathGeometry);
_secondhandSpriteShape.FillBrush = _compositor.CreateColorBrush(Colors.Tomato);
_secondhandSpriteShape.Offset = new Vector2(100f, 5f);
_secondhandSpriteShape.CenterPoint = new Vector2(0.0f, 95.0f);

The soccer ball graphic at the end of the hour and minute hands is drawn from an SVG path. The background clock face uses the exact same code – with a different path of course. CompositionProToolkit comes with its own parser for the path mini language. Here’s how the path is transformed into a sprite:

pathData = "M12.255911,27.32522L8.0630484,27.526361z ... (rest of path omitted)";
var spriteShape = _compositor.CreateSpriteShape(pathData);
spriteShape.FillBrush = _compositor.CreateColorBrush(Colors.DarkSlateGray);
spriteShape.Offset = new Vector2(84.0f, 30f);

If you consider using Win2D or Windows Composition in your UWP apps, you should definitely take a look at CompositionProToolkit!

Using a CompositionContainerShape

This last version of the clock also comes with a nice use case for the new CompositionContainerShape. The hour and minute hands are each a ‘group’ of two sprites: a soccer ball and a line to the center of the clock. Both shapes are combined into one:

_hourContainerShape = _compositor.CreateContainerShape();
_hourContainerShape.CenterPoint = new Vector2(100f, 100f);
// ...
var spriteShape = _compositor.CreateSpriteShape(pathData);
// ...
_hourContainerShape.Shapes.Add(spriteShape);

var line = _compositor.CreateLineGeometry();
// ...
spriteShape = _compositor.CreateSpriteShape(line);
// ...
_hourContainerShape.Shapes.Add(spriteShape);

For the animation, we can now use that container as a target. So we don’t have to animate the individual shapes – very convenient:

_hourContainerShape.RotationAngleInDegrees = (float)now.TimeOfDay.TotalHours * 30;
_minuteContainerShape.RotationAngleInDegrees = now.Minute * 6;

The Sample Project

The sample project lives here on Github.

Enjoy!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s