CompositionProToolkit v0.5.1 released : Win2d Mini Language

cpt_banner

Well, this is the first update for CompositionProToolkit in 2017, and it has turned out to be a huge update. I continued to build upon the work which I had started in the previous update i.e. parsing the SVG path language to convert it to CanvasGeometry. Now I am happy to announce that in this version, I have added more features to the existing path language, more commands to represent new shapes, brushes and strokes. It has now evolved into a more robust language which I am terming as the Win2d Mini Language.

In this post, I shall be explaining the work done to incorporate Win2d Mini Language into CompositionProToolkit and other related features.

ICanvasStroke and CanvasStroke

In Win2d, the stroke, that is used to render an outline to a CanvasGeometry, is comprised of three components

  • Stroke Width – defines the width of the stroke.
  • Stroke Brush – defines the ICanvasBrush that will be used to render the stroke.
  • Stroke Style – defines the CanvasStrokeStyle for the stroke.

ICanvasStroke interface, defined in the CompositionProToolkit.Win2d namespace, encapsulates these three components and the CanvasStroke class implements this interface. It provides several constructors to define the stroke.

public interface ICanvasStroke
{
  ICanvasBrush Brush { get; }
  float Width { get; }
  CanvasStrokeStyle Style { get; }
  Matrix3x2 Transform { get; set; }
}

public sealed class CanvasStroke : ICanvasStroke
{
  public float Width { get; }
  public CanvasStrokeStyle Style { get; }
  public ICanvasBrush Brush { get; }
  public Matrix3x2 Transform { get; set; }
  public CanvasStroke(ICanvasBrush brush, float strokeWidth = 1f);
  public CanvasStroke(ICanvasBrush brush, float strokeWidth, CanvasStrokeStyle strokeStyle);
  public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth = 1f);
  public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth, 
                      CanvasStrokeStyle strokeStyle);
}

The Transform property in CanvasStroke gets or sets the Transform property of the stroke brush.

IGeometrySurface

The IGeometrySurface interface is now updated to include ICanvasStroke to specify the definition of the stroke to be rendered on its geometry.

The following APIs are provided in ICompositionGenerator to create a IGeometrySurface

IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    ICanvasStroke stroke);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry, 
    Color fillColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, Color fillColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color fillColor, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, Color fillColor, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush fillBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, ICanvasBrush fillBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush fillBrush, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, ICanvasBrush fillBrush, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color fillColor, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasStroke stroke, Color fillColor, ICanvasBrush backgroundBrush);    
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color foregroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color foregroundColor, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush foregroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush foregroundBrush, ICanvasBrush backgroundBrush);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    ICanvasBrush foregroundBrush, Color backgroundColor);
IGeometrySurface CreateGeometrySurface(Size size, CanvasGeometry geometry,
    Color foregroundColor, ICanvasBrush backgroundBrush);

IGeometrySurface now provides several additional APIs which allow you to update its geometry, size, stroke, fill and background (and thus the shape of the Visual).

void Redraw(CanvasGeometry geometry);
void Redraw(ICanvasStroke stroke);
void Redraw(Color fillColor);
void Redraw(ICanvasStroke stroke, Color fillColor);
void Redraw(Color fillColor, Color backgroundColor);
void Redraw(ICanvasStroke stroke, Color fillColor, Color backgroundColor);
void Redraw(ICanvasBrush fillBrush);
void Redraw(ICanvasStroke stroke, ICanvasBrush fillBrush);
void Redraw(ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
void Redraw(ICanvasStroke stroke, ICanvasBrush fillBrush, 
    ICanvasBrush backgroundBrush);
void Redraw(Color fillColor, ICanvasBrush backgroundBrush);
void Redraw(ICanvasStroke stroke, Color fillColor, 
    ICanvasBrush backgroundBrush);
void Redraw(ICanvasBrush fillBrush, Color backgroundColor);
void Redraw(ICanvasStroke stroke, ICanvasBrush fillBrush, 
    Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke);
void Redraw(Size size, CanvasGeometry geometry, Color fillColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke, 
    Color fillColor);
void Redraw(Size size, CanvasGeometry geometry, Color fillColor, 
    Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    Color fillColor, Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasBrush fillBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasBrush fillBrush,
    ICanvasBrush backgroundBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    ICanvasBrush fillBrush, ICanvasBrush backgroundBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasBrush fillBrush,
    Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    ICanvasBrush fillBrush, Color backgroundColor);
void Redraw(Size size, CanvasGeometry geometry, Color fillColor,
    ICanvasBrush backgroundBrush);
void Redraw(Size size, CanvasGeometry geometry, ICanvasStroke stroke,
    Color fillColor, ICanvasBrush backgroundBrush);

CanvasDrawingSession extension methods

The following extension methods have been created for CanvasDrawingSession to incorporate ICanvasStroke in its DrawXXX() methods

public static void DrawCircle(this CanvasDrawingSession session,
    Vector2 centerPoint, float radius, ICanvasStroke stroke);
public static void DrawCircle(this CanvasDrawingSession session,
    float x, float y, float radius, ICanvasStroke stroke);
public static void DrawEllipse(this CanvasDrawingSession session, 
    Vector2 centerPoint, float radiusX, float radiusY, ICanvasStroke stroke);
public static void DrawEllipse(this CanvasDrawingSession session,
    float x, float y, float radiusX, float radiusY, ICanvasStroke stroke);
public static void DrawGeometry(this CanvasDrawingSession session,
    CanvasGeometry geometry, ICanvasStroke stroke);
public static void DrawGeometry(this CanvasDrawingSession session,
    CanvasGeometry geometry, Vector2 offset, ICanvasStroke stroke);
public static void DrawGeometry(this CanvasDrawingSession session,
    CanvasGeometry geometry, float x, float y, ICanvasStroke stroke);
public static void DrawLine(this CanvasDrawingSession session, Vector2 point0, 
    Vector2 point1, ICanvasStroke stroke);
public static void DrawLine(this CanvasDrawingSession session, float x0, 
    float y0, float x1, float y1, ICanvasStroke stroke);
public static void DrawRectangle(this CanvasDrawingSession session, Rect rect, 
    ICanvasStroke stroke) ;
public static void DrawRectangle(this CanvasDrawingSession session, float x, 
    float y, float w, float h, ICanvasStroke stroke);
public static void DrawRoundedRectangle(this CanvasDrawingSession session, 
    Rect rect, float radiusX, float radiusY, ICanvasStroke stroke);
public static void DrawRoundedRectangle(this CanvasDrawingSession session, float x,
    float y, float w, float h, float radiusX, float radiusY, ICanvasStroke stroke);

Win2d MiniLanguage specification

The CanvasGeometry class facilitates the drawing and manipulation of complex geometrical shapes. These shapes can be outlined with a stroke and filled with a brush (which can be a solid color, a bitmap pattern or a gradient).

While the CanvasGeometry class provides various static methods to create predefined shapes like Circle, Ellipse, Rectangle, RoundedRectangle, the CanvasPathBuilder class provides several methods to create freeform CanvasGeometry objects.

Creation of a complex freeform geometric shape may involve invoking of several CanvasPathBuilder commands. For example, the following code shows how to create a triangle geometry using CanvasPathBuilder

CanvasPathBuilder pathBuilder = new CanvasPathBuilder(device);

pathBuilder.BeginFigure(1, 1);
pathBuilder.AddLine(300, 300);
pathBuilder.AddLine(1, 300);
pathBuilder.EndFigure(CanvasFigureLoop.Closed);

CanvasGeometry triangleGeometry = CanvasGeometry.CreatePath(pathBuilder);

Win2d Mini Language is a powerful and sophisticated language which facilitates specifying complex geometries, color, brushes, strokes and stroke styles in a more compact manner.

Using Win2d Mini-Language, the geometry in above example can be created in the following way

string pathData = “M 1 1 300 300 1 300 Z”;
CanvasGeometry triangleGeometry = CanvasObject.CreateGeometry(device, pathData);

Win2d Mini Language now contains additional commands which allow easy creation of ICanvasBrush, ICanvasStroke, Color and CanvasStrokeStyle.

Win2d Mini Language is based on the SVG (Scalable Vector Graphics) Path language specification.

The following specification document describes the Win2d Markup Language in detail.

Win2d Mini Language Specification.pdf

Parsing Win2d Mini Language with CanvasObject

The CanvasObject static class replaces the CanvasGeometryParser static class and provides APIs that parse the Win2d Mini Language and instantiate the appropriate objects.

Color

From Hexadecimal Color string

There are two APIs that convert the hexadecimal color string in #RRGGBB or #AARRGGBB format to the corresponding Color object. The ‘#‘ character is optional.

public static Color CreateColor(string hexColor);
public static bool TryCreateColor(string hexColor, out Color color);

The first API will raise an ArgumentException if the argument is not in the correct format while the second API will attempt to convert the color string without raising an exception.

From High Dynamic Range Color string

The following API Converts a Vector4 High Dynamic Range Color to Color object. Negative components of the Vector4 will be sanitized by taking the absolute value of the component. The HDR Color components should have value in the range [0, 1]. If their value is more than 1, they will be clamped at 1. Vector4’s X, Y, Z, W components match to Color’s R, G, B, A components respectively.

public static Color CreateColor(Vector4 hdrColor);

CanvasGeometry

The following API converts a CanvasGeometry path string to CanvasGeometry object

public static CanvasGeometry CreateGeometry(ICanvasResourceCreator resourceCreator,
                                            string pathData, 
                                            StringBuilder logger = null);

The logger parameter in this method is an option argument of type StringBuilder which can be used to obtain the CanvasPathBuilder commands in text format. It is mainly intended for information/debugging purpose only.

NOTE: This method replaces CanvasGeometryParser.Parse() method (defined in CompositionProToolkit v0.5).

ICanvasBrush

The following API converts a brush data string to ICanvasBrush object

public static ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator, 
                                       string brushData);

CanvasStrokeStyle

The following API converts a style data string to CanvasStrokeStyle object

public static CanvasStrokeStyle CreateStrokeStyle(string styleData);

ICanvasStroke

The following API converts a stroke data string to ICanvasStroke object

public static ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator,
                                         string strokeData);

CanvasPathBuilder extension methods

Two extension methods have been added to CanvasPathBuilder to add a Rectangle Figure and a RoundedRectangle figure to the path.

public static void AddRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y,
    float width, float height)
public static void AddRoundedRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y,
    float width, float height, float radiusX, float radiusY);

Check out the Sample Gallery project where you can interact with the CanvasObject class by providing the SVG/XAML path data and converting it to CanvasGeometry. You can alter the StrokeThickness, StrokeColor and FillColor of the rendered geometry.

You can view the CanvasPathBuilder commands called to create the parsed geometry.

Creating multilayer Vector shapes with CanvasElement

The CanvasElement class allows the creation of multilayer vector shapes. Each layer is represented by the CanvasRenderLayer class. The CanvasRenderLayer implements the ICanvasRenderLayer interface which encapsulates three properties required for rendering a layer

  • CanvasGeometry – The geometry to be rendered on the layer.
  • ICanvasBrush – The brush to fill the geometry.
  • ICanvasStroke – The stroke to outline the geometry.

The CanvasRenderLayer provides several constructors which accept the CanvasGeometry, ICanvasBrush and ICanvasStroke objects or their respective data definition strings.

public CanvasRenderLayer(CanvasGeometry geometry, ICanvasBrush brush,
    ICanvasStroke stroke);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    string brushData, string strokeData);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    ICanvasBrush brush, Color strokeColor, float strokeWidth = 1f);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    ICanvasBrush brush, Color strokeColor, float strokeWidth, 
    CanvasStrokeStyle strokeStyle);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData,
    ICanvasBrush brush, ICanvasBrush strokeBrush, float strokeWidth = 1);
public CanvasRenderLayer(ICanvasResourceCreator creator, string geometryData, 
    ICanvasBrush brush, ICanvasBrush strokeBrush, float strokeWidth, 
    CanvasStrokeStyle strokeStyle);

The CanvasElement class implements the ICanvasElement interface which provides the following properties and APIs

interface ICanvasElement
{
    List Layers { get; set; }
    bool ScaleStroke { get; set; }
void Render(CanvasDrawingSession session, float width, float height, Vector2 offset,
    Vector4 padding, float rotation);

SpriteVisual CreateVisual(ICompositionGenerator generator, float width, float height,
    Vector2 offset, Vector4 padding, float rotation);

}

The Render API renders the CanvasElement layers on a CanvasControl or a CanvasAnimated control for the given dimensions, offset, padding and rotation.

The CreateVisual API creates a SpriteVisual which contains several SpriteVisuals, each representing a layer of the CanvasElement.

The constructor of CanvasElement requires the base dimensions of the element, the layers of the CanvasElement and an option whether to scale the stroke width when the CanvasElement is scaled (default is true).

public CanvasElement(float baseWidth, float baseHeight, IEnumerable layers,
            bool scaleStroke = true);

Using the base dimension, the CanvasLayer is able to scale its layers to any valid dimensions.

The layers are rendered based on their order in the layers list, i.e. the first layer in the list is rendered first and the subsequent layers are drawn on top of the previous layer. Thus the first layer in the list appears at the bottom and the last layer in the list is rendered top most.

CanvasElement can be primarily used to create vector based icons for UWP applications.

The following example code

CanvasElement _element;

private void OnCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
    var geom1 = CanvasObject.CreateGeometry(sender, "O 116 116 128 128");
    var fill1 = CanvasObject.CreateBrush(sender, "SC #00adef");
    var stroke1 = CanvasObject.CreateStroke(sender, "ST 8 SC #2a388f");
    var layer1 = new CanvasRenderLayer(geom1, fill1, stroke1);
    
    var geom2 = CanvasObject.CreateGeometry(sender, "U 56 56 64 64 8 8");
    var fill2 = CanvasObject.CreateBrush(sender, "SC #ed1c24");
    var stroke2 = CanvasObject.CreateStroke(sender, "ST 2 SC #404041");
    var layer2 = new CanvasRenderLayer(geom2, fill2, stroke2);

    var geom3 = CanvasObject.CreateGeometry(sender, "U 136 56 64 64 8 8");
    var fill3 = CanvasObject.CreateBrush(sender, "SC #38b449");
    var layer3 = new CanvasRenderLayer(geom3, fill3, stroke2);

    var geom4 = CanvasObject.CreateGeometry(sender, "U 56 136 64 64 8 8");
    var fill4 = CanvasObject.CreateBrush(sender, "SC #fff100");
    var layer4 = new CanvasRenderLayer(geom4, fill4, stroke2);

    var geom5 = CanvasObject.CreateGeometry(sender, "R 96 96 64 64");
    var fill5 = CanvasObject.CreateBrush(sender, "SC #f7931d");
    var layer5 = new CanvasRenderLayer(geom5, fill5, stroke2);

    var layers = new List { layer1, layer2, layer3, layer4, layer5 };
    _element = new CanvasElement(256f, 256f, layers);
}

private void OnCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    _element?.Render(args.DrawingSession, 512f, 512f, Vector2.Zero, new Vector4(10), 0f);
}

will create the following

Utility methods and Constants

Float constants

The following floating point constants have been defined in the static class Float

  • Float.Pi – same as (float)Math.PI radians (180 degrees).
  • Float.TwoPi – Two times (float)Math.PI radians (360 degrees).
  • Float.PiByTwo – half of (float)Math.PI radians (90 degrees).
  • Float.PiByThree – one third of (float)Math.PI radians (600 degrees).
  • Float.PiByFour – one fourth of (float)Math.PI radians (45 degrees).
  • Float.PiBySix – one sixth of (float)Math.PI radians (30 degrees).
  • Float.ThreePiByTwo – three times half of (float)Math.PI radians (270 degrees).
  • Float.DegreeToRadians – 1 degree in radians.
  • Float.RadiansToDegree – 1 radian in degrees.

ToSingle extension method

The Single() extension method for System.Double is now marked as obsolete. Your code will still work, but you will receive a warning during build.

The Single() extension method is now replaced with ToSingle() extension method. It does the same job – converts System.Double to System.Single.

The latest code is available in GitHub and the Nuget package is available here.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s