Monday, August 2, 2010

WPF UIElements Effects

WPF controls are lookless. We can apply different styles / templates to drastically change the appearance of the control. We can also update these styles and templates based on certain user or system events. These UIElements also supports different effects to be applied to them. We can define various effects like we can define the buttons to have shadows. We might want to blur / swirl them to be replaced by any other UIElement. This provides richness to the user interface as well it allows creation of cool animations.

Assembly:
System.Windows.Media.Effects

Built-In effects:
.Net framework 3.5 sp1 released with two built-in effects for UIElements. Both of these effects inherit from Effect class in System.Windows.Media.Effects namespace. That means they both use hardware based rendering. They are as follows:
  1. Drop Shadow effect
  2. Blur effect

Drop Shadow Effect:
We can control the color, opacity, depth and direction of the shadow. The Direction is double value and its value is specified in degrees.
Let us create a button with Burlywood shadow color. We are not specifying the value of Direction so the runtime would consider its default value (315 degrees)
<Button Height="33" Margin="35,40,52,0" Name="button1" VerticalAlignment="Top" Content="Button with Drop Shadow Effect">
<Button.Effect>
<DropShadowEffect ShadowDepth="5" Color="BurlyWood" Opacity=".4" RenderingBias="Performance" />
</Button.Effect>
</Button>



Blur Effect:
The other built-in effect available is BlurEffect. For setting gaussian blur, we can set KernelType property to Gaussian. The more we set the value of Radius the more blurred the UIElement would be rendered.
<Button Margin="35,116,83,112" Name="button2" Content="Button with Blur effect">
<Button.Effect>
<BlurEffect Radius="2" KernelType="Gaussian" />
</Button.Effect>
</Button>


Note:
Like the example of Button above, other UIElements also support effects but the behavior might be a little different e.g. when DropShadowEffect is applied to a textblock, each character has a drop shadow instead of the whole UIElement.
<TextBlock Height="29" Margin="36,0,64,58" Name="textBlock1" VerticalAlignment="Bottom" Text="text block" >
<TextBlock.Effect>
<DropShadowEffect />
</TextBlock.Effect>
</TextBlock>



GPU based Acceleration:
These effects are hardware acclerated. The processing capability of GPU is used to process these effects. GPUs use pixel shaders to generate those effects. As you can see in my post below that these GPU operations are SIMD operations. The use of SIMD operation logic makes the operations execution real fast.

http://shujaatsiddiqi.blogspot.com/2010/05/microsoft-accelerator-research-project.html

We can also think about the possibility of leveraging Microsoft Accelerator as discussed in above post for these effects.

Custom Effects:
All WPF hardware accelerated effects are inherited from Effect class . The DropShadowEffect and BlurEffect are both inherited from the same class. In order to create custom effects, we need to inherit from ShaderEffect, which also inherits from Effect class.



You might find other names like BitmapEffect. They use software based rendering. They also require full trust permissions. They also have other issues with runtime updates in the visual hierarchies. These are the issues resulting them being obsolete in framework 4.0.

To create custom effects, we need to write the code for pixel shaders which is written in HLSL, a C-based language. Now this can be difficult if we start writing HLSL code by hand. This can be easier if we could use some utilities and tools provided by WPF / Silverlight community (e.g. Shazzam).

http://shazzam-tool.com/

We simply need two items from shazzam. First is the pixel shader (*.ps) and a class inheriting from ShaderEffect which uses this pixel shader. We can provide shazzam the HLSL code in the form of a *.fx file or just copy and paste on Shazzam window. As soon as we compile it, Shazzam generates both of them for us. We can also test the effect by testing harness provided.

There are different sample shaders available with Shazam. Let's use Swirl shader. As discussed above, compiling it would generate Swirl.ps [Tools -> Explore Compiled Shaders (*.ps)] and C# code.


Add the shader (*.ps) file to any folder and add it as an existing item to your project. Change the Build Action for this file to "Resource". Now copy and paste the C# code to any code file in you project.



Now we can use this effect class (SwirlEffect) as an effect for our UIElements. Let's use it for a button control:

<Button Height="37" Margin="38,0,34,12" Name="button3" VerticalAlignment="Bottom"
Content="Button with Swirl effect">
<Button.Effect>
<local:SwirlEffect SpiralStrength="0.5" AspectRatio="1.5" />
</Button.Effect>
</Button>

This button would look like this:

Animating Effects:
Like other UIElements' properties, effect's properties can also be animated. Let us animate the the first button which uses the drop shadow effect.

<Button Height="33" Margin="35,40,52,0" Name="button1" VerticalAlignment="Top"
Content="Button with Drop Shadow Effect">
<Button.Style>
<Style>
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Effect.Color"
From="#FF231244" To="#FFFFFFFF"
Duration="0:0:1"
/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Effect.Color"
From="#FFFFFFFF" To="#FF231244"
Duration="0:0:1"
/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Effect>
<DropShadowEffect ShadowDepth="5" Color="BurlyWood" BlurRadius="2" Opacity=".4"
RenderingBias="Performance" />
</Button.Effect>
</Button>


Now as soon as you mouse over the button, it changes its drop shadows. As soon as you take mouse away, it reverts back to its original state.



Similarly, we can create animation for Blur effect as follows:
<Button Margin="35,116,83,112" Name="button2" Content="Button with Blur effect">
<Button.Style>
<Style>
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Effect.Radius"
From="2" To="50"
Duration="0:0:1"
/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Effect.Radius"
From="50" To="2"
By="1"
Duration="0:0:1"
/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Effect>
<BlurEffect Radius="2" KernelType="Gaussian" x:Name="GEffect" />
</Button.Effect>
</Button>

The mouse over effect of above animation should be as follows:


Note:
For simplicity of example, we have created these animations like these. You can create these styles anywhere in the resource section of the logical hierarchy. They can also be defined in the resource dictionary incase you want to share these styles across different applications.

Performance considerations:
Effects are cool. They provide richness to WPF based applications. But they can be expensive. I mean, e.g. it is recommended that instead of DropShadowEffect for simple UIElement we can use another Geometry shape along with UIElement. Basically what happens when we apply effects on UIElement, WPF runtime rasterizes the UIElement into a bitmap and apply operations on each pixel. They might be simple arithmetic, trigonometric or any other operations. Applying these operations might affect the responsiveness of the application.

Unlike DirectX, WPF has the fallback mechanism of using Software rendering pipeline for effects if pixel shaders are not supported by the GPU in the machine running the application. This is really a great but we must test our applications on such systems before releasing them for client so that we could find out if they have acceptable responsiveness under all the possible platforms.