XAML Attached Properties Composition Effects

Hello, today I made my first XAML step with Composition. This is the XAML result after making alchemy with XAML, Composition and the Win2D effects.

saturation1

The idea is to create Effect attachable properties to add for instance a saturated background that can even follow binding saturation changes and size changes.

beach

Prerequisites

– I did not find any NuGet of the Microsoft.UI.Composition.Toolkit, so I copy it from the composition-master from the Microsoft Windows Composition repository, you should download it and reference for the project.
– Learn a bit about Effects, just their properties and what they do, you can take a look to the official documentation and in my ‘old’ Win2D posts:
Win2d Posts
– Know what are attached properties and how to implement that in XAML.
– Know what are dependency properties and how to add to a dependency object and user them in XAML.

Common code for composition

If you know a bit of the prerequisites, now you need how to get the visual layer from a UIElement, the compositior and how to create the CompositionImageFactory. All of these are required to manipulate the visual layer:

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

    public static Compositor GetCompositor(Visual visual)
    {
        return visual.Compositor;
    }

    public static CompositionImageFactory CreateCompositionImageFactory(Compositor compositor)
    {
        return CompositionImageFactory.CreateCompositionImageFactory(compositor);
    }
}

Saturation class

Now we know how to gain access to the visual layer, let’s create a Dependency object class called Saturation that will have the following fields and properties:

Element

Is the attached element for the Saturation class

#region Element
private UIElement element;
private FrameworkElement frameworkElement;
#endregion

Source

Is the dependency property that will take as Image Source for our Saturation Effect

#region Source
public string Source
{
    get { return (string)GetValue(SourceProperty); }
    set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
    DependencyProperty.Register(nameof(Source), typeof(string), typeof(Saturation), 
		new PropertyMetadata(null));
#endregion

Composition

We need to get the Visual Composition layer of the Element:

Image from blog.windows.com

#region Composition
private ContainerVisual visual;
private Compositor compositor;
private CompositionImageFactory imageFactory;
#endregion

Variable Effect

We want to change the value of the Saturation in the Saturation Effect, to make that, we need to set the Saturation as ‘animatable’. We have to set a name for the SaturationEffect and know the path of the Saturation (like when we use Storyboards).

#region Effect
private string EffectSource = "EffectSource";
private string SaturationEffect = "SaturationEffect";
private string SaturationEffectPath = "SaturationEffect.Saturation";
private SaturationEffect effect;
private CompositionEffectFactory effectFactory;
#endregion

Composition Brush and Image

This is the image we will create

#region CompositionImage
private CompositionSurfaceBrush surfaceBrush;
private CompositionImage imageSource;
#endregion

What we paint on the composition image

We have to create the image with the effect using the following:

#region Brush & Sprite
private CompositionEffectBrush effectBrush;
private SpriteVisual spriteVisual;
#endregion

Some of these fields might sound a bit confusing, but I made the methods simple and step by step to make things clear.

Initialization

When the attached property sets (I will explain later) we will call to this method.

public void Initialize(UIElement attachedelement)
{
    element = attachedelement;
    frameworkElement = element as FrameworkElement;
    CreateImageFactory(element);
    CreateEffect();
    CreateSurface();
    CreateBrush();
    CreateSpriteVisual();
    Insert();
    DetectElementChange();
}

Where CreateImageFactory is an standard method to get the visual, compositor and create an imageFactory to work:

using static Universal.Managers.CompositionManager;

private void CreateImageFactory(UIElement element)
{
    visual = GetVisual(element);
    compositor = GetCompositor(visual);
    imageFactory = CreateCompositionImageFactory(compositor);
}

To make code more readable the CompositionManager class that is common to for any effect you need I added as static.

Creating the Effect

This step is important to understand:

  • To create an effect that can change a property during the runtime, we need to assign to the Effect a name.
  • To add a source to the effect that comes from composition instead from Win2D we assign a CompositionEffectSourceParameter
  • Update Level is the value that converts percentage to 0.0 to 1.0f values and sets to the animatable property.
  • Effect Factory creates the effect with the path defined before to allow us to change the saturation level.
private void CreateEffect()
{
    effect = new SaturationEffect()
    {
        Name = SaturationEffect,
        Saturation = 0.0f,
        Source = new CompositionEffectSourceParameter(EffectSource)
    };

    UpdateLevel();
    effectFactory = compositor.CreateEffectFactory(effect, new[] { SaturationEffectPath });
}

(UpdateLevel method is explained later).

Surface, Brush and SpriteVisual

private void CreateSurface()
{
    surfaceBrush = compositor.CreateSurfaceBrush();
    var uriSource = UriManager.GetFilUriFromString(Source);
    imageSource = imageFactory.CreateImageFromUri(uriSource);
    surfaceBrush.Surface = imageSource.Surface;
}

private void CreateBrush()
{
    effectBrush = effectFactory.CreateBrush();
    effectBrush.SetSourceParameter(EffectSource, surfaceBrush);
    effectBrush.Properties.InsertScalar(SaturationEffectPath, 0f);
}

private void CreateSpriteVisual()
{
    spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Brush = effectBrush;
    spriteVisual.Size = new Vector2(GetElementWidth(), GetElementHeight());
}

Where GetElementWidth and GetElementHeight are safe methods to avoid NaN from the Element values.

Level of Saturation

The Level dependency property:

#region Level
public double Level
{
    get { return (double)GetValue(LevelProperty); }
    set { SetValue(LevelProperty, value); }
}

public static readonly DependencyProperty LevelProperty =
    DependencyProperty.Register(nameof(Level), typeof(double), typeof(Saturation), new PropertyMetadata(0.0, LevelChanged));

private static void LevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    (d as Saturation).UpdateLevel();
}

private void UpdateLevel()
{
    if (effectBrush != null)
    {
        var newvalue = (float)(Level / 100.0);
        effectBrush.Properties.InsertScalar(SaturationEffectPath, newvalue);
    }
}
#endregion

Insert the composition created and detect size changes

Finally let’s add the composition to the Element in its visual layer and detect its size changes:

private void Insert()
{
    visual.Children.InsertAtBottom(spriteVisual);
}

private void DetectElementChange()
{
    frameworkElement.SizeChanged += (s, e) =>
    {
        UpdateSpriteVisual();
    };
}

Conclusions

Composition allows to change parameters and follow size changes without recreating all the image, effect, etc. which makes the interface more responsive and efficient.
It is similar to animate/change an effect value as it is were a Storyboard.
The image source of an effect is using CompositionEffectSourceParameter.
You have to get the visual layer from an UIElement using the methods in the CompositionManager.

Source code

The source code is available on GitHub in the UniversalExamples-SaturationEffectPage

It is my first step in Composition, in following articles I will take a look how to add more effects, animate plenty of equal instances and many more.

To be up to date @juanpaexpedite and happy coding.

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