UWP Composition Effects: Hue Rotation

In this article we play around with the Hue Rotation effect, which is one of the UWP Composition Effects. This effect allows you to apply a rotation to all of the colors of an image in real-time. The effect takes one parameter -the rotation angle- which is a float value between 0 and 2π.

The best way to demonstrate the Hue Rotation, is to apply it on an image of a color wheel. By changing the angle of the effect, the image seems to rotate physically. Here are some screenshots of the sample app that I wrote. It applies the Hue Rotation effect on some images. The rotation angle of the effect is controlled by a slider:

ColorWheel_1

ColorWheel_2

ColorWheel_3

When you move the slider slowly from its minimum to its maximum value, you’ll see that the image appears to rotate clockwise. It looks like a RotateTransform, but we’re actually just shifting the colors of the bitmap. The color shift is not linear however: for some rotation angles the yellow range entirely disappears – like in the screenshot in the middle. That’s because in color theory nothing is linear: everything is based on non-linear physical characteristics as well as on biological ones (how the human eye works). There is an awesome and well-readable introduction to all of this right here.

So rotating colors may look easy at first sight, but it’s definitely not. The Hue Rotation effect actually applies a color matrix in the red-green-blue space. The matrix depends on the rotation angle.

Enough introduction, let’s fire up Visual Studio now.

Preparing the project

The core components of the Composition API are built into the UWP platform. On top of that, the sample app makes use of the Microsoft UI Composition Toolkit. That’s a small helper library, written in C++, that facilitates loading bitmap images into composition visuals:

CompositionToolkit

When you want to use one or more of the Composition API effects in your UWP app, then you also need to add the Win2D UWP Nuget package:

Win2D_uwp_Nuget

One line of XAML

The Composition API sits between XAML and DirecX, so XAML-wise there is not much to do: it suffices to define a host element for the image. A named Grid control will do:

<Grid x:Name="Container" Margin="0" />

Creating and applying the effect

For the C# part, we start with defining some fields in the View’s code-behind. We define

private Compositor _compositor;
private ContainerVisual _root;
private CompositionImageFactory _imageFactory;
private SpriteVisual _spriteVisual;
private CompositionEffectBrush _brush;

When the page is loaded, we initialize the infrastructure:

// Initialize Composition UI infrastructure.
_root = Container.GetVisual();
_compositor = _root.Compositor;
_imageFactory = 
	CompositionImageFactory.CreateCompositionImageFactory(_compositor);

We make use of the following extension method to create the placeholder:

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;
    }
}

Here’s how to create the sprite visual, and hook it into the XAML visual tree:

// Hook the sprite visual into the XAML visual tree.
_spriteVisual = _compositor.CreateSpriteVisual();
var side = (float)Math.Min(
	Presenter.ActualWidth, Presenter.ActualHeight);
_spriteVisual.Size = new Vector2(side, side);
_root.Children.InsertAtTop(_spriteVisual);

All the previous code is further explained in this article of mine on the basics of the Composition API.

Let’s now dive into the effect related code. The next couple of steps create the Hue Rotation effect:

  • We first define the effect, where we specify the name of the image brush that will be used as Source, but we omit the Angle.
  • Then we compile it with CreateEffectFactory() where we specify the parameterized properties (i.c. the Angle) in an EffectName.PropertyName syntax, and
  • create a brush that holds the effect, through CreateBrush().
  • Finally we apply that brush to the sprite visual:
// Create the effect, but don't specify the Angle yet.
var hueRotationEffect = new HueRotationEffect
{
    Name = "hueRotation",
    Source = new CompositionEffectSourceParameter("source")
};

// Compile the effect
var effectFactory = _compositor.CreateEffectFactory(
	hueRotationEffect, 
	new[] { "hueRotation.Angle" });

// Create and apply the brush.
_brush = effectFactory.CreateBrush();
_spriteVisual.Brush = _brush;

An effect needs pixels to work on, so it is always chained to a CompositionSurfaceBrush which can hold a color, a bitmap, or an effect.

Here’s how to load a bitmap into a brush:

private void LoadImage(Uri uri)
{
    // Create CompositionSurfaceBrush
    var surfaceBrush = _compositor.CreateSurfaceBrush();

    // Create an image source to load
    CompositionImage imageSource = _imageFactory.CreateImageFromUri(uri);
    surfaceBrush.Surface = imageSource.Surface;

    _brush.SetSourceParameter("source", surfaceBrush);
}

The last line of the previous snippet  –the SetSourceParameter() call- connects the effect brush to the image brush.

When we created the effect, we parameterized the hue rotation angle. This allows animation and programmatic access. When the rotation angle changes, there’s no need to reload or redraw the bitmap. We only need to apply a new value to the angle. :

private void RotateHue(float angle)
{
    // Apply parameter to brush.
    _brush.Properties.InsertScalar("hueRotation.Angle", angle);
}

We now have a nice playground to test the effect on different images.

What about black and white?

The Hue Rotation effect should not recolor black, grey, or white pixels, since these are not on the color wheel. And indeed, when we load a black and white gradient image into the sprite visual and apply the effect, nothing changes:

BlackAndWhite

A real life example

Here’s an example of where the Hue Rotation effect could be used in an app. Let’s take a look at the Color Picker from WinRT XAML Toolkit in action:

xamlcolorpicker

For a human it’s hard to find or define a color by just using sliders for the amount of red, green, and blue (RGB). Most color pickers use another standard to describe the same set of colors: HSL or HSV, where

  • H stands for Hue. This is the color’s position on the color wheel, generally expressed in degrees from 0° to 359° where 0° represents red and 180° corresponds to red’s opposite color – cyan.
  • S stands for Saturation, the purity of the color, or how far it is from any shade of grey. It is expressed as a percentage.
  • L and V stand for Lightness and Value (a.k.a Brightness). These are two different ways of specifying how far away the color is from black. Both are expressed as a percentage.

For more definitions (chroma, luma, luminance, colorfulness and so on) and more exact definitions and formulas, start reading here.

The color wheel on the outside of the color picker, allows you to easily pick the base color (the Hue). The triangle on the inside manages the two other properties: Brightness and Saturation.

If you dive into the code of the WinRT XAML Toolkit Color Picker Sample, you’ll notice that after every Hue change, the inner triangle is entirely redrawn pixel by pixel. The control  calls the RenderColorPickerSaturationLightnessTriangleCore() method in WriteableBitmapColorPickerExtensions. When the size of the control changes, the same algorithm is applied and everything is redrawn pixel by pixel.

All in all, that’s a pretty heavy operation, and I can imagine that some hardware –like a cheap phone or an IoT device- will choke on this.

This is a scenario where the Composition API and the Hue Rotation effect in particular can come to the rescue. The following set of screenshots show the impact of Hue Rotation on a monochromatic image. The main color of the image is changed but the white, black, and grey tones are unaffected, and that’s exactly what the color picker needs:

BrightnessAndSaturation_1

BrightnessAndSaturation_2

BrightnessAndSaturation_3

The image is never redrawn, only its colors are shifted. I can confirm that it runs smoothly on the phone:

HueRotationPhone

The code

The sample app is here on GitHub. It will be extended with demos of other Composition API effects.

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 )

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