Monthly Archives: May 2016

UWP Composition Effects: Chaining

The number of UWP Composition Effects is relatively limited -especially compared to the total list of Win2D effects. But you can roll your own custom effect by building an effect factory that combines two or more effects.  In this short article we’ll explain how a XAML UWP app can combine –or chain- multiple Composition Effects on a single image, and update properties on (one of) these effects dynamically.

Here are some screenshots of the corresponding sample app.

  • The image on the left shows a configurable SaturationEffect. This effect allows you adapt the saturation of the image – how far the colors are from any shade of grey. The saturation can be changed by the slider underneath.
  • The middle image shows the result of an InvertEffect that … inverts the colors of the same base image.
  • The image on the right combines the two effects:

Chaining0

Chaining1

The steps to load an image, apply any of the Composition Effects on it and display the result inside a XAML element, were explained in previous blog posts. So allow me to fast-forward through the process. The same steps are also explained on MSDN, but the sample app executes them in a totally different order. As long as you build a decent effect graph, it will display properly.

Here’s how the sample app does it:

_leftSpriteVisual = _compositor.CreateSpriteVisual();
var side = (float)Math.Min(LeftPresenter.ActualWidth, LeftPresenter.ActualHeight);
_leftSpriteVisual.Size = new Vector2(side, side);
_leftRoot.Children.InsertAtTop(_leftSpriteVisual);
// Create CompositionSurfaceBrush
_imageBrush = _compositor.CreateSurfaceBrush();

// Create an image source to load
var imageSource = _imageFactory.CreateImageFromUri(
	new Uri("ms-appx:///Assets/flowers.jpg"));
_imageBrush.Surface = imageSource.Surface;

Here’s the code for the saturation effect, with the saturation value as a dynamic property:

// Create and apply the first effect.
_firstEffect = new SaturationEffect
{
    Name = "firstEffect",
    Source = new CompositionEffectSourceParameter("source")
};
var firstEffectFactory = _compositor.CreateEffectFactory(
	_firstEffect, 
	new[] { "firstEffect.Saturation" });
_effectBrush1 = firstEffectFactory.CreateBrush();
_leftSpriteVisual.Brush = _effectBrush1;

Here’s the code for the invert effect, without any configuration:

// Create and apply the second effect.
var secondEffect = new InvertEffect
{
    Name = "secondEffect",
    Source = new CompositionEffectSourceParameter("source")
};
var secondEffectFactory = _compositor.CreateEffectFactory(secondEffect);
_effectBrush2 = secondEffectFactory.CreateBrush();
_middleSpriteVisual.Brush = _effectBrush2;

And finally, here’s the code to create the combined effect. Observe that it is just an InvertEffect that takes the SaturationEffect as Source:

// Create and apply the combined effect.
_combinedEffect = new InvertEffect
{
    Name = "chained",
    Source = _firstEffect
};
var combinedEffectFactory = _compositor.CreateEffectFactory(
	_combinedEffect, 
	new[] { "firstEffect.Saturation" });
_combinedBrush = combinedEffectFactory.CreateBrush();
_rightSpriteVisual.Brush = _combinedBrush;

Observe that we reuse the same image brush instance in all three images:

// Chain the brushes
_effectBrush1.SetSourceParameter("source", _imageBrush);
_effectBrush2.SetSourceParameter("source", _imageBrush);
_combinedBrush.SetSourceParameter("source", _imageBrush);

The image that is loaded into the first effect (the source of the combined effect) is tied to the first effect factory, NOT the combined effect factory. So you do have to load the image into the combined brush.

  • Update the dynamic properties. These are the properties that you will set on-the-fly, with or without animation. Here’s the code behind the slider:
private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    if (_effectBrush1 != null)
    {
        // Apply parameter to brushes.
        _effectBrush1.Properties.InsertScalar(
	"firstEffect.Saturation", 
	(float)e.NewValue);
        _combinedBrush.Properties.InsertScalar(
	"firstEffect.Saturation", 
	(float)e.NewValue);
    }
}

Again, the dynamic properties for the first effect brush are tied to the first effect factory, and hence do not apply to the combined effect. So you have to update the dynamic properties on the combined brush. The sample app needs to update two brushes, because it displays the two separate images.

That app lives here on GitHub.

Enjoy!