Using Effects in WPF, Part 2
In a previous post on Effects gave a simple example of instantiating a
parameter-less Effect. It just took the complement of a color through XAML. Now let's
discuss more about the use of Effects.
First, lest you think that Effects are somehow a XAML-only feature, let’s write some code
to use Effects. Recall that the invocation of the Effect through XAML looked like this:
<Grid>
<Grid.Effect>
<eff:ColorComplementEffect />
</Grid.Effect>
<Image ...="" />
<Button ...="" />
<TextBox ...="" />
</Grid>
In code, presuming I already have the Grid and it's called "myGrid", we’d just do the following:
myGrid.Effect = new MyEffects.ColorComplementEffect();
Like everything in WPF, Effects can be created and manipulated in code, so most of the below will focus on usage through XAML.
The ColorComplementEffect is very limited in what it can do, as it takes no parameters. Let's look at using an Effect that does
take parameters. This is an intensity-thresholding effect. For each pixel, it determines if the pixel is above a certain intensity
and, if so, outputs that pixel; otherwise, it outputs a constant color (transparent by default). The XAML below applies the ThresholdEffect
with a threshold value of 0.2, and yellow as the color to fill in those pixels that are below an average 0.2 intensity.
<Grid>
<Grid.Effect>
<eff:ThresholdEffect Threshold="0.25" BlankColor="Orange" />
</Grid.Effect>
<Image ...="" />
<Button ...="" />
<TextBox ...="" />
</Grid>
The result looks like this:
The ThresholdEffect class exposes Threshold and BlankColor as DependencyProperties, so we can use our
standard WPF techniques to databind and animate these properties. For instance, the XAML below introduces
a slider and wires the Threshold value of the ThresholdEffect to the value of the slider. As you move the
slider, the grid content "thresholds" in and out:
<Grid>
<Grid.Effect>
<eff:ThresholdEffect Threshold="{BindingElementName=thresholdSlider, Path=Value}"
BlankColor="Orange" />
</Grid.Effect>
<Image ...="" />
<Button ...="" />
<TextBox ...="" />
</Grid>
<Slider Name="thresholdSlider" Minimum="0" Maximum="1"...=""/>
Chaining Effects
In the world of GPU programming, there’s a concept known as "multi-pass", where the programmer
sets up multiple pixel shaders and cycles through them, with the output of one becoming the input of the next.
In this release of WPF, we do not have explicit support for building multi-pass effects. However, WPF provides
a very natural way to express what is typically desired, namely "containment". If I want to apply my
ThresholdEffect as above; but, after that, I want to apply the ColorComplementEffect, a very simple way to do
this is to take the Grid shown above and wrap a Border around it. On this Border, add the ColorComplementEffect.
Here's the XAML:
<Border>
<Border.Effect>
<eff:ColorComplementEffect />
</Border.Effect>
<Grid >
<Grid.Effect>
<eff:ThresholdEffect Threshold="0.25" BlankColor="Orange" />
</Grid.Effect>
<Image ...="" />
<Button ...="" />
<TextBox ...=""/>
</Grid>
</Border>
Here's what this looks like:
Multi-input Effects
These Effects receive a single input bitmap from the UIElement they are attached to and can have
an arbitrary number of "scalar" properties through DependencyProperties that the Effect
author provides for his users, and that they can be chained via element containment. These "scalar"
properties are things like double, Color, Point, Point3D, Vector, Vector3D, Size, etc.
One thing that all the Effects have in common is they have exactly one bitmap input: the rasterization
of the UIElement they're being applied to. WPF also supports "multi-input" effects where
multiple UIElements (or other sources of bitmap data) can be provided to and manipulated by an Effect.
(We don't consider effects like ThresholdEffect to be multi-input even though they have multiple
properties/parameters. We consider "multi-input" to mean receiving more than one bitmap input.)
Multi-input effects will be part of the .NET 3.5 SP1 release, but they're not in the Beta, so we won't discuss them further here.