Exercise 1 – Pixel Shaders and Implicit Styles

During this exercise, we will apply implicit styles, a new feature in Silverlight 4 which allows a style to be applied to all elements of matching type. Following this, we will apply pixel shader effects to an image and animate various properties of these effects. These custom effects include Ripple, Invert, Swirl and Pixelate. Finally we will apply a perspective transform to the canvas and animate it to yield a 3D planar effect.

Task 1 - Implicit Styles

In Silverlight 4 you can create Implicit Styles in addition to Explicit Styles. This new feature allows programmers to override default styles for common and third party controls with their own style, without the need to use explicit keys. In this exercise we will use supplied style and change the default look and feel of the application.

  1. In App.xaml, in the Application resources section, locate the Style element. Remove the x:Key attribute and run the application.

    XAML

    <Style TargetType="Button"> …
    Note:
    Notice how the Button controls implicitly pick up the style via its TargetType, without having to explicitly specify the Style key resource, or rely on the implicit style manager. The style contains an associated ControlTemplate, which threads the Button contents through a ContentPresenter.
  2. Open MainPage.xaml, select the “Swirl” Button and explicitly set its Style to null, to opt out of the explicit style.

    XAML

    <Button Content="Swirl" Click="Button_Click" Style="{x:Null}"/>
  3. Run the application again – notice that the particular button is no longer picking up the style.

Task 2 - Pixel Shader Effects

Silverlight 3 introduced Pixel Shader Effects, including the ability to add custom effects. Silverlight 3 supports 2 native effects – the blur effect and the drop shadow effect. Every UIElement has an Effect property. An effect outputs a modification of the pixel that is at the same location in the input texture as its destination in the output texture, manipulate color but not position. The Effect class contains an EffectMapping method, which must be overridden by derived methods to return a GeneralTransform that takes a position and returns the post-effect corresponding position.

In order to create a custom effect, use the effects compiler (fxc.exe) from the DirectX SDK to compile a high level shading language (HLSL) .fx file into a .ps file, which is included in the Silverlight project, with its Build Action set to Resource. A .NET class must be created to represent the custom effect, which derives from ShaderEffect and provides a default constructor to initialize the effect.

The WPF Pixel Shader Effects library is available from https://wpffx.codeplex.com/ and can be adapted to work with Silverlight. Pixel Shader Effects are exposed via the System.Windows.Media.Effects namespace.

Open the Shaders solution folder. Note that there are 8 items in the folder: 4 *.ps files and 4 matching class files. These comprise 4 pixel shader effects: InvertColorEffect, RippleEffect, InvertEffect and SwirlEffect.

Figure 1

Pixel Shares in Project Structure

Note:
A ShaderEffect class provides a custom bitmap effect using a PixelShader. Each custom Pixel Shader Effect inherits from ShaderEffect and loads a shader model 2 pixel shader. Dependency properties declared in this class are mapped to registers defined in an associated *.ps file. The constructor points to the *.ps file as a relative Uri and creates an instance of the PixelShader. The UpdateShaderValue method notifies the effect that the shader constant or sampler corresponding to the specified dependency property should be updated.
  1. Open the InvertColorEffect and examine the class contents.

    C#

    namespace MagicMirror.ShaderEffectLibrary { using System; using System.Windows; using System.Windows.Media; using System.Windows.Media.Effects; using System.Diagnostics; public class InvertColorEffect : ShaderEffect { #region Dependency Properties public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(InvertColorEffect), 0); #endregion #region Member Data private static PixelShader pixelShader; #endregion #region Constructors static InvertColorEffect() { pixelShader = new PixelShader(); pixelShader.UriSource = new Uri("/MagicMirror;component/Shaders/InvertColor.ps", UriKind.Relative); } public InvertColorEffect() { this.PixelShader = pixelShader; UpdateShaderValue(InputProperty); } #endregion public Brush Input { get { return (Brush)GetValue(InputProperty); } set { SetValue(InputProperty, value); } } } }
  2. Open MainPage.xaml.cs and locate the Button_Click event handler. It contains a switch statement for determining the selected button. We are going to fill in the body of each case, in order to set the pixel shader effect applied to the Path Fill image, and to start a storyboard which will animate tweening of appropriate effect properties.

    C#

    private void Button_Click(object sender, RoutedEventArgs e) { switch ((sender as Button).Content.ToString()) { case "Pixelate": //TODO: start storyboard and set Path's effect break; case "Swirl": //TODO: start storyboard and set Path's effect break; case "Invert": //TODO: start storyboard and set Path's effect break; case "Ripple": //TODO: start storyboard and set Path's effect break; default: TO_FILL.Effect = null; break; } }
  3. In MainPage.xaml, add a XAML namespace mapping for the .NET namespace containing the custom shader effects:

    XAML

    xmlns:shaders="clr-namespace:MagicMirror.ShaderEffectLibrary"
  4. We are now ready to utilize the custom pixel shader effects within the application.

Task 3 - InvertColorEffect

  1. In MainPage.xaml, in “UserControl.Resources” section, declaratively add the InvertColorEffect, giving it a name. Save the file.

    XAML

    <shaders:InvertColorEffect x:Name="effectInvert" />
  2. Open MainPage.xaml.cs and locate the Button_Click event handler. In the Invert case, set the Path’s Effect to the InvertColorEffect.

    C#

    case "Invert": TO_FILL.Effect = effectInvert; break;
  3. Run the application and click the Invert button. Notice that the image colors are inverted.

Task 4 - PixelateEffect

  1. In MainPage.xaml, in “UserControl.Resources” section, declaratively add the PixelateEffect, giving it a name, and values for its HorizontalPixelCount and VerticallPixelCount properties. Save the file.

    XAML

    <shaders:PixelateEffect x:Name="effectPixelate" HorizontalPixelCounts="48" VerticalPixelCounts="48"/>
  2. Create a Storyboard with 2 animations for animating these properties.

    XAML

    <Storyboard x:Name="sbPixelate" Storyboard.TargetName="effectPixelate" AutoReverse="True" RepeatBehavior="Forever"> <DoubleAnimation Duration="00:00:04" Storyboard.TargetProperty="HorizontalPixelCounts" From="48" To="256" /> <DoubleAnimation Duration="00:00:04" Storyboard.TargetProperty="VerticalPixelCounts" From="48" To="256" /> </Storyboard>
  3. Open MainPage.xaml.cs and locate the Button_Click event handler. In the Pixelate case, set the Path’s Effect to the PixelateEffect, and start the Storyboard animation.

    C#

    case "Pixelate": sbPixelate.Begin(); TO_FILL.Effect = effectPixelate; break;
  4. Run the application and click the Pixelate button. Notice that the image appears to be pixilated in an animated fashion.

Task 5 - SwirlEffect

  1. In MainPage.xaml, in “UserControl.Resources” section, declaratively add the SwirlEffect, giving it a name. Save the file.

    C#

    <shaders:SwirlEffect x:Name="effectSwirl" />
  2. Create a Storyboard with an animation for animating the Swirl.Factor property.

    C#

    <Storyboard x:Name="sbSwirl" Storyboard.TargetName="effectSwirl" RepeatBehavior="Forever" AutoReverse="True"> <DoubleAnimation Duration="00:00:02" Storyboard.TargetProperty="Factor" From="-2" To="2" /> </Storyboard>
  3. Open MainPage.xaml.cs and locate the Button_Click event handler. In the Swirl case, set the Path’s Effect to the SwirlEffect, and start the Storyboard animation.

    C#

    case "Swirl": sbSwirl.Begin(); TO_FILL.Effect = effectSwirl; break;

Task 6 - RippleEffect

  1. In MainPage.xaml, in “UserControl.Resources” section, declaratively add the RippleEffect, giving it a name. Save the file.

    XAML

    <shaders:RippleEffect x:Name="effectRipple" />
  2. Create a Storyboard with an animation for animating the RippleEffect.Phase property.

    XAML

    <Storyboard x:Name="sbRipple" Storyboard.TargetName="effectRipple" Storyboard.TargetProperty="Phase"> <DoubleAnimation From="30.0" To="0" Duration="00:00:10" RepeatBehavior="Forever" /> </Storyboard>
  3. Add a MouseMove event handler to the canvas named “borderEffects” and create the corresponding event handler for the event:

    XAML

    MouseMove="borderEffects_MouseMove"
  4. On the corresponding code behind page, set the RippleEffect.Center property according to MouseMoveEventArgs.GetPosition, which returns the (x,y) coordinate position (as a Point struct) evaluated against the supplied Canvas UIElement.

    C#

    private void borderEffects_MouseMove(object sender, MouseEventArgs e) { Point mousePt = e.GetPosition(this); effectRipple.Center = new Point(mousePt.X / this.ActualWidth, mousePt.Y / this.ActualHeight); }
  5. In MainPage.xaml.cs locate the Button_Click event handler. In the Ripple case, set the Path’s Effect to the RippleEffect, and start the Storyboard animation.

    C#

    case "Ripple": sbRipple.Begin(); TO_FILL.Effect = effectRipple; break;
  6. Run the application and click the Ripple button.
  7. Move the mouse over the image and observe the ripple effect (repeated by the storyboard) and that its center is set according to the mouse position.

Task 7 - Stop Animations

  1. Open MainPage.xaml.cs.
  2. Add the following code into the stopAnimations() method:

    C#

    private void stopAnimations() { sbPixelate.Stop(); sbRipple.Stop(); sbSwirl.Stop(); }
  3. At the end of this task, the complete Button_Click method should look like this:

    C#

    private void Button_Click(object sender, RoutedEventArgs e) { stopAnimations(); switch ((sender as Button).Content.ToString()) { case "Pixelate": sbPixelate.Begin(); TO_FILL.Effect = effectPixelate; break; case "Swirl": sbSwirl.Begin(); TO_FILL.Effect = effectSwirl; break; case "Invert": TO_FILL.Effect = effectInvert; break; case "Ripple": sbRipple.Begin(); TO_FILL.Effect = effectRipple; break; default: TO_FILL.Effect = null; break; } }
  4. At the end of this exercise we have pixel shader effects applied to the picture in the frame. We just learned how to use the provided pixel shader effects in order to provide the end user with live picture processing functionality and enrich the application’s UI.

Task 8 - Projection

Silverlight 3 introduced a Projection property on UIElement which can be set to a PlaneProjection, an implementation of a 3D PerspectiveTransform. The UIElement that is projected into this 3D scene is interactive even if projected. Both front and back are interactive. The transforms that PlaneProjection applies to its UIElement are: 

  • A TranslateTransform exposed via LocalOffsetX, LocalOffsetY, LocalOffsetZ in PlaneProjection.
  • A set of RotateTransform represented by the CenterOfRotationX, CenterOfRotationY and CenterOfRotationZ  and the RotationX, RotationY and RotationZ properties.
  • A TranslateTransform(3D), exposed via GlobalOffsetX, GlobalOffsetY, GlobalOffsetZ.

In this exercise we will apply plane projection for our application.

  1. In MainPage.xaml , Add a Canvas.Projection then create a PlaneProjection and give it a name as shown next:

    XAML

    <Canvas x:Name="borderEffects" MouseMove="borderEffect_MouseMove"> <Canvas.Projection> <PlaneProjection x:Name="borderProjection"/> </Canvas.Projection> …
  2. Create a Storyboard that targets the PlaneProjection. Use this to animate the following 3 properties: RotationX, RotationY, and RotationZ.

    XAML

    <Storyboard x:Name="sbPerspective" Storyboard.TargetName="borderProjection"> <DoubleAnimation AutoReverse="False" RepeatBehavior="Forever" From="0" To="360" Duration="00:00:05" Storyboard.TargetProperty="RotationX"/> <DoubleAnimation AutoReverse="False" RepeatBehavior="Forever" From="0" To="360" Duration="00:00:05" Storyboard.TargetProperty="RotationY"/> <DoubleAnimation AutoReverse="False" RepeatBehavior="Forever" From="0" To="360" Duration="00:00:05" Storyboard.TargetProperty="RotationZ"/> </Storyboard>
  3. In MainPage.xaml.cs , locate the btnRotate event handler and implement logic to Start / stop the storyboard.

    C#

    private void btnRotate_Click(object sender, RoutedEventArgs e) { if (btnRotate.Content.ToString() == "Rotate") { btnRotate.Content = "Stop"; sbPerspective.Begin(); } else { btnRotate.Content = "Rotate"; sbPerspective.Stop(); } }
  4. Run the application. Select a pixel shader effect and click the Rotate button. The perspective rotation starts swirling around.
  5. Set the Canvas.CacheMode to BitmapCache.

    XAML

    <Canvas x:Name="borderEffects" MouseMove="borderEffects_MouseMove" CacheMode="BitmapCache">
  6. Run the application again. Select a pixel shader effect and click the Rotate button. The perspective rotation starts swirling around.