CompositionProToolkit v0.6 Released!

cpt_banner

Well, it has been almost 6 months since my last post. I have been busy working on a few apps and learning a few new technologies. In the meantime, Windows 10 Creators update was released, Build 2017 happened and Microsoft introduced the Fluent Design System (a much needed feature for Windows 10 UX). The Windows Composition team also open sourced the Expression Builder code which allows you to define the expressions in ExpressionAnimations in a more intuitive way.

A couple of weeks ago, I felt it was time to revisit my code in CompositionProToolkit and update it to the latest Windows SDK features. CompositionProToolkit version 0.5.x releases were more biased towards Win2d. So I decided to dedicate version 0.6 to the CompositionProToolkit.Expressions namespace.

cpte_banner

I had a look at the Expression Builder code to understand its features. It helps the developers to create type-safe expressions using ExpressionNode (and its derivatives). I was wondering if there was a way to modify the authoring feature in such a way that the ExpressionNode (and its derivatives) remain hidden from view to the developer and s/he may use a more simpler way to specify the expression with type-safety (using Lambda expressions).

It was then I realized the code in CompositionProToolkit.Expressions namespace did not support creation of expression templates.

I realized that there was a huge scope of improvement in the CompositionProToolkit.Expressions namespace and it was possible to introduce templates within the lambda expressions.

Thus, I am happy to announce that with the release of CompositionProToolkit v0.6, Expression parsing is more robust now. A lot of extension methods have been added to help the developer minimize the code written to create animations.

Defining the ExpressionAnimation and KeyFrameAnimation

The following extension methods have been defined on the Compositor to create the appropriate ExpressionAnimation object.

public static ExpressionAnimation CreateColorExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateQuaternionExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateScalarExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateVector2ExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateVector3ExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateVector4ExpressionAnimation(this Compositor compositor);
public static ExpressionAnimation CreateMatrix4x4ExpressionAnimation(this Compositor compositor);

The following extension method is defined for the Compositor to create a KeyFrameAnimation object for the appropriate animating property

public static KeyFrameAnimation GenerateColorKeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateQuaternionKeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateScalarKeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateVector2KeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateVector3KeyFrameAnimation(this Compositor compositor);
public static KeyFrameAnimation GenerateVector4KeyFrameAnimation(this Compositor compositor);

After creating the ExpressionAnimation object you can set its Expression property with the appropriate Expression.

The following extension methods allow StartAnimation to be called directly using an ExpressionAnimation or a KeyFrameAnimation object.

public static void StartAnimation(this CompositionObject compositionObject,
    Expression expression, KeyFrameAnimation keyframeAnimation);
public static void StartAnimation(this CompositionObject compositionObject,
    Expression expression, ExpressionAnimation expressionAnimation);

Using CompositionPropertySet within the Expression

In order to use a CompositionPropertySet within the Expression the following GetXXX extension methods have been defined

public static bool GetBoolean(this CompositionPropertySet propertySet, string key);
public static Color GetColor(this CompositionPropertySet propertySet, string key);
public static Matrix3x2 GetMatrix3x2(this CompositionPropertySet propertySet, string key);
public static Matrix4x4 GetMatrix4x4(this CompositionPropertySet propertySet, string key);
public static Quaternion GetQuaternion(this CompositionPropertySet propertySet, string key);
public static float GetScalar(this CompositionPropertySet propertySet, string key);
public static Vector2 GetVector2(this CompositionPropertySet propertySet, string key);
public static Vector3 GetVector3(this CompositionPropertySet propertySet, string key);
public static Vector4 GetVector4(this CompositionPropertySet propertySet, string key);

Expression Targets and Templates

It is now possible to use the new operator inside expressions. You can also define Expression Targets and Expression Templates within the Expression.

Expression Targets allow you to access the composition object (on which the expression animation is executed) within the expression.

Expression Templates allow you to reuse an expression by connecting them to different references (thus creating separate animations). It is similar to the way C# Generics allows you to define a class template and reuse the template for different types.

Here is an example

var visual = _compositor.CreateSpriteVisual();
visual.Offset = new Vector3(20, 30, 40);
visual.Size = new Vector2(200, 100);

var delta = new Vector3(50);

var offsetAnimation = _compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = 
    c => new VisualReference("myVisual").CenterPoint + new Vector3(10, 20, 30) + delta;
offsetAnimation.SetReference("myVisual", visual);

visual.StartAnimation(() => visual.CenterPoint, offsetAnimation);

var visual2 = _compositor.CreateSpriteVisual();
visual2.Offset = new Vector3(10, 10, 10);
visual2.Size = new Vector2(100, 100);

offsetAnimation.SetReference("myVisual", visual2);
offsetAnimation.SetReference("delta", new Vector3(100));

visual2.StartAnimation(() => visual2.CenterPoint, offsetAnimation);

Using Arrays in Expression

You can use an Array of objects deriving from CompositionObject within your Expression.

Example

var visual1 = compositor.CreateSpriteVisual();
...
var visual2 = compositor.CreateSpriteVisual();
...
var visualArray = new Visual[] { visual1, visual2 };

var offsetAnimation = compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = c => visualArray[0].Offset + new Vector(20);

visualArray[1].StartAnimation(() => visualArray[1].Offset, offsetAnimation);

Using List<> in Expression

You can use a List<> of objects deriving from CompositionObject within your Expression.

Example

var visual1 = compositor.CreateSpriteVisual();
...
var visual2 = compositor.CreateSpriteVisual();
...
var visualList = new List { visual1, visual2 };

var offsetAnimation = compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = c => visualList[0].Offset + new Vector(20);

visualList[1].StartAnimation(() => visualList[1].Offset, offsetAnimation);

Using Dictionary<,> in Expression

You can use a Dictionary<TKey, TValue> within your Expression.

TKey can be of types – int, float, double or string.

TValue should be an object deriving from CompositionObject.

Example

var visual1 = compositor.CreateSpriteVisual();
...
var visual2 = compositor.CreateSpriteVisual();
...
var visualDictionary = new Dictionary<string, Visual> 
    {
        ["first"] = visual1, 
        ["second"] = visual2 
    };

var offsetAnimation = compositor.CreateVector3ExpressionAnimation();
offsetAnimation.Expression = c => visualDictionary["first"].Offset + new Vector(20);

visualDictionary["second"].StartAnimation(() => visualDictionary["second"], offsetAnimation);

Custom Cubic Bezier Easing Functions

The following extension methods have been added to Compositor to create predefined CubicBezierEasingFunctions (these custom cubic bezier easing functions are based on the Robert Penner’s Easing Equations and the values are obtained from Ceaser CSS Easing Animation Tool )

public static CubicBezierEasingFunction CreateEaseInBackEasingFunction();
public static CubicBezierEasingFunction CreateEaseInCircleEasingFunction();
public static CubicBezierEasingFunction CreateEaseInCubicEasingFunction();
public static CubicBezierEasingFunction CreateEaseInExponentialFunction();
public static CubicBezierEasingFunction CreateEaseInQuadraticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInQuarticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInQuinticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInSineEasingFunction();

public static CubicBezierEasingFunction CreateEaseOutBackEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutCircleEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutCubicEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutExponentialFunction();
public static CubicBezierEasingFunction CreateEaseOutQuadraticEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutQuarticEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutQuinticEasingFunction();
public static CubicBezierEasingFunction CreateEaseOutSineEasingFunction();

public static CubicBezierEasingFunction CreateEaseInOutBackEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutCircleEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutCubicEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutExponentialFunction();
public static CubicBezierEasingFunction CreateEaseInOutQuadraticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutQuarticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutQuinticEasingFunction();
public static CubicBezierEasingFunction CreateEaseInOutSineEasingFunction();

DeviceLostHelper

CompositionProToolkit now uses the DeviceLostHelper class (provided in the WindowsUIDevLabs Sample Gallery application) to watch for CanvasDevice and it raises an appropriate event when the device is lost.

Project structure and Nuget

CompositionProToolkit v0.6 also revamps the project structure. The CompositionProToolkit.Ref project has been discarded and the CompositionDeviceHelper project has been added. It is a C++/CX project which encapsulates the DeviceLostHelper class.

The CompositionProToolkit.nuspec has also been updated. Now it includes the XML documentation file to provide better Intellisense support when using CompositionProToolkit in your code. I had some issues creating a single Nuget package targeting x86, x64 & ARM – which internally contained a UWP library and a Windows Runtime Component. There is no clear documentation provided for this scenario. I had to refer through numerous blogs and forum posts to finally get the nuget structure right. I will write in more detail about this in my next blog post.

Source Code, Nuget & Documentation

Happy Coding!

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.

CompositionProToolkit v0.4.6 released

Another update to CompositionProToolkit this week with couple of feature additions to ImageFrame and some bug fixes.

ImageFrame

Optimized Shadow

ImageFrame now provides a new dependency property called OptimizeShadow which allows multiple ImageFrame instances to share the same DropShadow instance. You can set this property to True when you are displaying several ImageFrame instances within a single container and each ImageFrame instance has shadow enabled. This will reduce the memory footprint.

Dependency Property Type Description Default Value
OptimizeShadow Boolean Indicates whether the ImageFrame should use a shared shadow object to display the shadow. False

IDisposable

ImageFrame now implements the IDisposable interface and thus provides a Dispose method to free up resources. ImageFrame internally uses several Composition objects which must be disposed to optimize the memory usage within your app.

Guidelines for effectively disposing ImageFrame

Consider a scenario where you are displaying several ImageFrame instances in a GridView within a page. When the app navigates away from the page, then you must dispose the ImageFrame instances like this

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    foreach (var imgFrame in ItemGridView.GetDescendantsOfType())
    {
        imgFrame.Dispose();
    }    
    VisualTreeHelper.DisconnectChildrenRecursive(ItemGridView);
    ItemGridView.ItemsSource = null;
    ItemGridView.Items?.Clear();

    base.OnNavigatedFrom(e);
}

Frosted Glass Effect Brush

A new extension method CreateFrostedGlassBrush has been added to Compositor which, as the name suggests, allows you to create a custom shaped brush which gives a frosted glass effect. The main difference between this method and the CreateMaskedBackdropBrush is that when you apply the FrostedGlassBrush to a visual with a DropShadow, it will look better, whereas with the MaskedBackdropBrush, the shadow will darken the visual.

FrostedGlassEffect.png

You can obtain the mask from the FrostedGlass effect brush, to apply to the DropShadow, in the following way

var roundRectGeometry = CanvasGeometry.CreateRoundedRectangle(_generator.Device, 
    0, 0, _width, _height, 25, 25);
var maskSurface = _generator.CreateMaskSurface(visual.Size.ToSize(),
     roundRectGeometry);
var frostedBrush = _compositor.CreateFrostedGlassBrush(maskSurface,
     Colors.AntiqueWhite, 30f, _backdropBrush);

var shadow = _compositor.CreateDropShadow();
shadow.Opacity = 0.5f;
shadow.Color = Colors.Black;
shadow.Offset = new Vector3(10, 10, 0);
shadow.BlurRadius = 15;

// Apply the mask to the shadow
shadow.Mask = frostedBrush.GetSourceParameter("mask");

visual.Brush = frostedBrush;
visual.Shadow = shadow;

The FrostedGlass effect brush was inspired from this blog post.

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

CompositionProToolkit v0.4.5 released!

The past few releases were mostly dedicated to refactoring and ImageFrame. CompositionProToolkit v0.4.5 continues the tradition and brings in new additions to code and a few new features to the ImageFrame control.

CompositionProToolkit.Expressions

cpte_banner.png

Well, this task was pending for a long time! CompositionExpressionToolkit code now has been merged into CompositionProToolkit under the CompositionProToolkit.Expressions namespace. If you have not used the CompositionExpressionToolkit project before you can find out more more details about CompositionProToolkit.Expressions here.

ImageFrame

In this release, ImageFrame has got a few feature modifications and additions.

ImageFrame Source

The Source property of ImageFrame is of now of type object and it can accept the following types

  • Uri
  • String (from which a Uri can be successfully created)
  • StorageFile
  • IRandomAccessStream

Image Caching

Image caching is permanently enabled now. ImageFrame will internally cache the objects provided to Source property. The cache is located in the temporary folder of the application using the ImageFrame.

As a result, the following property has been deprecated and removed from ImageFrame

  • UseImageCache

You can clear the cache anytime you want by using the following code

await ImageCache.ClearCacheAsync();

ImageFrame Transitions

CPTT_1.gif

ImageFrame provides several transition animations while displaying the newly loaded image. You can configure which animation to run by setting the TransitionMode property of the imageFrame.

Dependency Property Type Description Default Value
TransitionMode TransitionModeType Indicates the type of transition animation to employ for displaying an image after it has been loaded. TransitionModeType.FadeIn

TransitionMode property can be set to any of the following values defined in the TransitionModeType enumeration

  • FadeIn – The newly loaded image fades into view.
  • SlideLeft – The newly loaded image slides into view from the right side of the ImageFrame and moves left.
  • SlideRight – The newly loaded image slides into view from the left side of the ImageFrame and moves right.
  • SlideUp – The newly loaded image slides into view from the bottom of the ImageFrame and moves up.
  • SlideDown – The newly loaded image slides into view from the top of the ImageFrame and moves down.
  • ZoomIn – The newly loaded image zooms into view from the center of the ImageFrame.

ImageFrame Placeholder

The ShowPlaceholder property is now set to True by default.

Latest source code is available in GitHub and Nuget package is available here.

CompositionProToolkit v0.4.4 released

Though CompositionProToolkit v0.4.4 was initially planned to be a small update aimed at fixing bugs and improving the user experience of the CompositionImageFrame, it turned out to be a larger update, ultimately requiring one of the oldest class in CompositionProToolkit to be refactored. Midway between the first phase of refactoring, I came up with another idea which led to the whole refactored code being discarded. I had to restart the refactoring from scratch and the final code appears to be much more streamlined than it was earlier. Another reason for refactoring was to shorten the names of the classes. Adding Composition to each of the classes resulted in very long names (CompositionSurfaceImageOptions for example).

Unfortunately, this refactoring led to some breaking changes again! But let me assure you, the pains that you will take to accommodate these breaking changes in your existing code will save a lot of headaches in the future.

Without much ado, let us delve into the details.

Breaking Changes

ICompositionMask

Of late, due to ever increasing requirements, I was adding more features to ICompositionMask which resulted in the class getting bloated. This class was playing a dual role – One, as the building block for creating a CompositionMaskBrush to render custom shaped visuals. Two, as the building block for creating a CompositionSurfaceBrush for rendering custom shaped geometries onto visuals.

Addition of new capabilities in ICompositionMask also introduced several flags in order to differentiate the two behaviors. It was high time to split them into two three interfaces –

  • IRenderSurface – This interface acts as the base interface for interfaces which render to the ICompositionSurface. It mainly contains references to an ICompositionGenerator object and an ICompositionSurface object which are the core objects required for rendering any geometry or image onto a ICompositionSurface.
  • IMaskSurface – This interface is used for rendering custom shaped geometries onto ICompositionSurface so that they can be useds as masks on Composition Visuals.
  • IGeometrySurface – This interface is used for rendering custom shaped geometries onto ICompositionSurface.

Both IMaskSurface and IGeometrySurface derive from IRenderSurface. Here is the interface hierarchy

RenderSurfaces

ICompositionSurfaceImage

If you look at the interface hierarchy image above, you will notice a fourth interface mentioned there – IImageSurface. It is actually the ICompositionSurfaceImage interface which has been refactored.

CompositionGenerator

CompositionGenerator‘s CreateMask APIs have been refactored into separate APIs for creating the three different types of render surfaces

IMaskSurface CreateMaskSurface(Size size, CanvasGeometry geometry);
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);
Task CreateImageSurfaceAsync(Uri uri, Size size, 
    ImageSurfaceOptions options);

CompositionImageFrame

CompositionImageFrame has been renamed to ImageFrame.

CompositionSurfaceImageOptions

The class with the longest name in CompositionProToolkit has now been renamed to ImageSurfaceOptions.

What’s new in CompositionProToolkit v0.4.4?

ImageFrame has been refactored with a better logical workflow. It has resulted in a better user experience. If you do not provide a definite Width and Height values to the ImageFrame, it will now size itself based on the loaded image and the parent control hosting the ImageFrame.

A new property RenderFast has been added to ImageFrame which gives you the option to turn off the animations which are used while rendering the image. This property is set to False by default. You can turn it on in scenarios where the Source property of the ImageFrame is being rapidly updated. For example, consider a ListBox containing numerous ImageFrames which is being scrolled very fast.

Property Type Description Default value
RenderFast Boolean Indicates whether the animations need to be switched off if the ImageFrame is being used in scenarios where it is being rapidly updated with new Source. False

NOTE: Do not confuse the RenderFast property with the RenderOptimized property. The former is used to turn off the animations during rendering, while the latter is used to reduces the memory footprint of the ImageFrame.

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

CompositionProToolkit v0.4.3 released!

Another busy week and I feel thrilled to announce the release of CompositionProToolkit v0.4.3. It mainly contains feature additions to CompositionImageFrame and some minor refactoring (no breaking changes!). Let’s delve into the details.

CompositionGeneratorFactory

CompositionGeneratorFactory APIs have been simplified further. Now there are two ways to obtain the CompositionGenerator – by providing a Compositor or by providing a CompositionGraphicDevice.

public static ICompositionGenerator GetCompositionGenerator(Compositor compositor,
    bool useSharedCanvasDevice = true, bool useSoftwareRenderer = false);
public static ICompositionGenerator GetCompositionGenerator(
    CompositionGraphicsDevice graphicsDevice);

The first API also has couple of optional parameters

  • useSharedCanvasDevice – indicates whether the CompositionGenerator should use a shared CanvasDevice or creates a new one.
  • useSoftwareRenderer – this parameter is provided as a argument when creating a new CanvasDevice (i.e. when usedSharedCanvasDevice is false).

ICompositionMask

Two new APIs have been added in ICompositionMask class which allow the redraw of the mask with a new Color or ICanvasBrush.

void Redraw(ICanvasBrush brush);
void Redraw(Color color);

CompositionImageFrame

CompositionImageFrame has received most attention in this release. Two of the most requested features – Placeholder and Image Caching, have now been incorporated into CompositionImageFrame. Also now whenever the AlignX or AlignY changes, the image animates to its new location.

When the image is being (cached and) loaded, the placeholder shows a progress bar which indicates the load progress.

Placeholder_progress

The following new properties have been added to CompositionImageFrame to configure the Placeholder and the Image Cache features.

Property Type Description Default value
ShowPlaceHolder Boolean Indicates whether the placeholder needs to be displayed during image load or when no image is loaded in the CompositionImageFrame. False
PlaceholderBackground Color Indicates the background color of the Placeholder. Colors.Black
PlaceholderColor Color Indicates the color with which the rendered placeholder geometry should be filled RGB(192, 192, 192)
UseImageCache Boolean Indicates whether the images should be cached before rendering them. True

CPT_8_1.gif

Using CompositionImageFrame with FilePicker

If you have a CompostionImageFrame control in you application and your want to use the FilePicker to select an image file to be displayed on the CompostionImageFrame, then you must do the following

var picker = new Windows.Storage.Pickers.FileOpenPicker
{
    ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail,
    SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary
};
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".jpeg");
picker.FileTypeFilter.Add(".png");
var file = await picker.PickSingleFileAsync();
ImageFrame.Source = await ImageCache.GetCachedUriAsync(file);

Since the CompositionImageFrame‘s Source property expects a Uri, while the FilePicker provides a StorageFile. So in order to obtain a Uri from the StorageFile, you must first cache it using the ImageCache.GetCachedUriAsync() method.

The latest source code is available in GitHub and NuGet package is available here.

CompositionProToolkit v0.4.2 released


Well, the last week has been pretty busy – learning little more details about the Composition APIs, major refactoring of the CompositionProToolkit code, breaking changes and adding new features and controls. The hard work finally paid off. I am excited to announce the release of CompositionProToolkit v0.4.2. Let’s see, in detail, what this new version contains.

Breaking Changes

First and foremost, you would like to know what will not work if you move to version 0.4.2. After digging deeper into the various classes within the Windows.UI.Composition namespace, I realized that before introducing new features and controls, it was high time I refactored my code. Thus I began – optimizing various classes, removing lot of redundant Task.Run(…)* encapsulations. As a result, many APIs which were earlier asynchronous are now synchronous (you no longer need to await them!). The main advantages are improved execution speed and more robust code. 🙂

Here are some of the classes whose APIs have changed. If you are using them within their code you will have to update them to the new API calls.

CompositionGeneratorFactory

CompositionGeneratorFactory no longer requires a sharedLock parameter while obtaining the CompositionGenerator. The lock is now managed internally within each CompositionSurfaceImage. Since this was an optional argument in the GetCompositionGenerator API, if you were not providing this argument in your code the your code need not be changed.

v0.4.1

public static ICompositionGenerator GetCompositionGenerator(Compositor compositor, 
    CompositionGraphicsDevice graphicsDevice=null, object sharedLock=null);

v0.4.2

public static ICompositionGenerator GetCompositionGenerator(Compositor compositor, 
    CompositionGraphicsDevice graphicsDevice=null);

ICompositionMask

All the asynchronous APIs in ICompositionMask class have been refactored into synchronous APIs.

v0.4.1

Task RedrawAsync();
Task RedrawAsync(Geometry.CanvasGeometry geometry);
Task RedrawAsync(Size size, Geometry.CanvasGeometry geometry);
Task RedrawAsync(Size size, Geometry.CanvasGeometry geometry, ICanvasBrush brush);
Task RedrawAsync(Size size, Geometry.CanvasGeometry geometry, Color color);
Task ResizeAsync(Size size);

v0.4.2

void Redraw();
void Redraw(Geometry.CanvasGeometry geometry);
void Redraw(Size size, Geometry.CanvasGeometry geometry);
void Redraw(Size size, Geometry.CanvasGeometry geometry, ICanvasBrush brush);
void Redraw(Size size, Geometry.CanvasGeometry geometry, Color color);
void Resize(Size size);

ICompositionSurfaceImage

A few of the asynchronous APIs in ICompositionSurfaceImage class have been refactored into synchronous APIs.

v0.4.1

Task RedrawAsync(CompositionSurfaceImageOptions options);
Task ResizeAsync(Size size);
Task ResizeAsync(Size size, CompositionSurfaceImageOptions options);

v0.4.2

void Redraw(CompositionSurfaceImageOptions options);
void Resize(Size size);
void Resize(Size size, CompositionSurfaceImageOptions options);

ICompositionGenerator

All the asynchronous APIs in ICompositionGenerator related to creating of ICompositionMask and visual Reflection have been refactored into synchronous APIs.

v0.4.1

Task CreateMaskAsync(Size size, CanvasGeometry geometry);
Task CreateMaskAsync(Size size, CanvasGeometry geometry, ICanvasBrush brush);
Task CreateMaskAsync(Size size, CanvasGeometry geometry, Color color);
Task CreateReflectionAsync(ContainerVisual visual, float reflectionDistance, 
    float reflectionLength, ReflectionLocation location);

v0.4.2

ICompositionMask CreateMask(Size size, CanvasGeometry geometry);
ICompositionMask CreateMask(Size size, CanvasGeometry geometry, ICanvasBrush brush);
ICompositionMask CreateMask(Size size, CanvasGeometry geometry, Color color);
void CreateReflection(ContainerVisual visual, float reflectionDistance, 
    float reflectionLength, ReflectionLocation location));

So what’s new in v0.4.2?

Now that we are done with the breaking changes, let’s look what all new features and controls have been added to CompositionProToolkit v0.4.2.

CompositionSurfaceImageOptions

CompositionSurfaceImageOptions now has an additional property AutoResize which can be set to False when you are trying to render a large image onto the CompositionSurfaceImage as a thumbnail. This will optimize memory usage.

Property Type Description Default value
AutoResize Boolean Specifies whether the CompositionSurfaceImage should resize itself automatically to match the loaded image size. When set to True, the Stretch, HorizontalAlignment and VerticalAlignment options are ignored. False

CompositionImageFrame

CompositionImageFrame now provides a fade in animation while transitioning from one image to another. It also supports rounded corners and display of shadows which can be configured easily. It also allows an option to optimize the rendering of the image. This is useful for scenarios where you are trying to render a large image to a smaller size, for example thumbnails.

CPT6.gif

The following new properties have been added to CompositionImageFrame

Property Type Description Default value
CornerRadius CornerRadius Indicates the corner radius of the the ImageFrame. The image will be rendered with rounded corners. (0, 0, 0, 0)
DisplayShadow Boolean Indicates whether the shadow for this image should be displayed. False
RenderOptimized Boolean Indicates whether optimization must be used to render the image.Set this property to True if the CompositionImageFrame is very small compared to the actual image size. This will optimize memory usage. False
ShadowBlurRadius Double Specifies the Blur radius of the shadow. 0.0
ShadowColor Color Specifies the color of the shadow. Transparent
ShadowOffsetX Double Specifies the horizontal offset of the shadow. 0.0
ShadowOffsetY Double Specifies the vertical offset of the shadow. 0.0
ShadowOpacity Double Specifies the opacity of the shadow. 1
TransitionDuration Double Indicates the duration of the crossfade animation while transitioning from one image to another. 700ms

FluidBanner

FluidBanner is now a part of the CompositionProToolkit. It now internally uses CompositionSurfaceImage to host the images. It provides the following properties which can be used to customize the FluidBanner.

Dependency Property Type Description Default Value
AlignX AlignmentX Indicates how the image is positioned horizontally in the FluidBanner items. Center
AlignY AlignmentY Indicates how the image is positioned vertically in the FluidBanner items. Center
DecodeHeight int The height, in pixels, that the images are decoded to. (Optional) 0
DecodeWidth int The width, in pixels, that the images are decoded to. (Optional) 0
ItemBackground Color The background color of each item in the FluidBanner Black
ItemGap double The gap between adjacent items in the banner. 30
ItemsSource IEnumerable<Uri> The collection of Uris of images to be shown in the FluidBanner null
Padding Thickness The padding inside the FluidBanner Thickness(0)
Stretch Stretch Indicates how the image is resized to fill its allocated space within each FluidBanner item. Uniform

The latest source code is available in GitHub. The Nuget package is available here