Home > Blend Modes, Pixel Shader Effects, WPF > Blend Modes, Part III

Blend Modes, Part III

June 30th, 2009

After I gave you the blend mode library for both WPF and Silverlight, you thought, “Finally! I can do some really cool stuff … and what’s even better … it will run on the GPU!” Then, you started playing with it a little and you ran into some limitations.

In this third blog post in the series (Part 1 and Part 2), I will explore these limitations and show you how you can use Jeremiah Morrill’s GlassBehavior (which with his permission I have renamed to BackgroundEffectBehavior) to get around some of them.

Let’s jump in.

Sometimes you’ll have two images of the exact same size that you want to blend together using one of the blend modes in my library. That is:

<!-- Blending Two Images Together -->
<Image
    Grid.Column="2"
    Width="325"
    Height="244"
    Source="Resources/summer_325x244.jpg"
>
    <Image.Effect>
        <bme:PhoenixEffect>
            <bme:PhoenixEffect.BInput>
                <ImageBrush ImageSource="Resources/fall_325x244.jpg"/>
            </bme:PhoenixEffect.BInput>
        </bme:PhoenixEffect>
    </Image.Effect>
</Image>

The above is easy. As you can see, you just choose one of the images and set the blend mode effect on it. This actually causes that image to become the ‘A’ input (remember: A + B = R). Then, you simply set the ‘B’ input of the blend mode effect to the second image.

But it isn’t always that easy, is it?!

Many times when a designer is thinking of blend modes they aren’t thinking “Ok, I have two images and I want to blend those together.” They often are thinking, “I have this shape and I want to blend it into its background using a cool blend mode.”

Take a look at the following. Here I have a shape, a gray (#FF808080) ‘H’, on top of an image:

HOnImage600

And, here I have the gray ‘H’, blended into its background image using the Color Burn blend mode:

HOnImageColorBurn600

Now, anywhere I move that ‘H’ … it is going to look different because it is being blended into its background, similar, of course, to making a shape transparent (although with different math).

So, how would I do this with the blend mode library that I’ve provided? Well, it would seem easy … and here is a valiant try:

<Grid Width="240" Height="150">
    <Grid.Resources>
        <SolidColorBrush x:Key="solidColorBrush" Color="#FF808080"/>
        <Path
            x:Key="path"
            Width="168.367"
            Height="152.44"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Stretch="Uniform"
            Data="(removed for clarity)"
            Fill="{DynamicResource solidColorBrush}"
        />
    </Grid.Resources>

    <Image
        Source="Resources/MSwanson - Wide - Water 06.jpg"
        Stretch="Uniform"
    >
        <Image.Effect>
            <bme:ColorBurnEffect>
                <bme:ColorBurnEffect.BInput>
                    <VisualBrush Visual="{StaticResource path}"/>
                </bme:ColorBurnEffect.BInput>
            </bme:ColorBurnEffect>
        </Image.Effect>
    </Image>
</Grid>

In the above, I am using the Image as the ‘A’ input and then passing the shape (Path) in as a VisualBrush for the ‘B’ input on the color burn blend mode effect. Here is what happens, though:

HOnImageColorBurn600NoCigar

What is going on? Well, if you recall from Greg Schlecter’s series (1 and 2) on multi-input pixel shader effects (which is how the blend modes are implemented), the inputs have to be either a VisualBrush or an ImageBrush and the inputs have to be the same size. If they are not, the second input is resized to match the first input.

So, our ‘H’ path is getting sized bigger to match the image’s size and the parts outside of the path are taking part in the blending that is occurring (i.e. the dark areas outside the ‘H’) as well.

Greg Schlecter suggests (in the aforementioned articles) how you can solve the ‘resizing’ issue … by using the Viewbox property on the brush.

But, let’s take a step back here. Do we really want to apply the blend mode effect to the background? No, we don’t, as that would limit us to having only one effect per background. For that matter, it is also not very intuitive as you typically think of blending the shape into the background which suggests that it makes more sense to apply effect to the shape.

While thinking about how to solve this problem, I ran into this WPF forum post. I first thought, “Oh, no! I’m up a creek without a paddle.” But, then I dove in on Jeremiah Morrill’s GlassBehavior … and got it too work (at least for certain situations)!

(I’m not going to cover behaviors in this blog post, but they are basically something new with Expression Blend 3.x. They are an implementation of the attached property behavior pattern and allow the designer inside of Blend to drag and drop behaviors onto elements in the element tree. See here and here for more info.)

Jeremiah’s GlassBehavior is a behavior that let’s you apply an effect to the background underneath the element that you are attaching the behavior to. He uses it to apply a SmoothMagnifyEffect (to get a glass like appearance) … but you can really use it to apply any effect. And that is just what I did.

Take a look at this xaml:

<Grid>
    <Grid x:Name="grid">
        <Image
            x:Name="image"
            Source="Resources/MSwanson - Wide - Water 06.jpg"
            Stretch="Uniform"
        />
    </Grid>
    <Canvas Width="168.367" Height="152.44">
        <Path
            Width="168.367"
            Height="152.44"
            Stretch="Uniform"
            Data="(removed for clarity)"
            Fill="{StaticResource solidColorBrush}"
        >
            <i:Interaction.Behaviors>
                <local:BackgroundEffectBehavior
                    Visual="{Binding ElementName=grid, Mode=OneWay}"
                >
                    <local:BackgroundEffectBehavior.Effect>
                        <bme:ColorBurnEffect>
                            <bme:ColorBurnEffect.BInput>
                                <ImageBrush>
                                <ImageBrush.ImageSource>
                                <DrawingImage>
                                <DrawingImage.Drawing>

                                <GeometryDrawing
                                    Brush="{StaticResource solidColorBrush}"
                                >
                                    <GeometryDrawing.Geometry>
                                        <RectangleGeometry Rect="0,0,1,1"/>
                                    </GeometryDrawing.Geometry>
                                </GeometryDrawing>

                                </DrawingImage.Drawing>
                                </DrawingImage>
                                </ImageBrush.ImageSource>
                                </ImageBrush>
                            </bme:ColorBurnEffect.BInput>
                        </bme:ColorBurnEffect>
                    </local:BackgroundEffectBehavior.Effect>
                </local:BackgroundEffectBehavior>
            </i:Interaction.Behaviors>
        </Path>
    </Canvas>
</Grid>

First of all, notice how the BackgroundEffectBehavior attaches to the Path through the attached property collection i:Interaction.Behaviors. This is how the behavior hooks its functionality into Path.

The BackgroundEffectBehavior’s Visual is set to the background (the grid which is holding the image) … and the BackgroundEffectBehavior’s Effect is set to the ColorBurnEffect which has a SolidColorBrush as its ‘B’ input. I also have the same SolidColorBrush set as the Fill of the Path … but this is just so that I can see it in the designer … as at runtime … this behavior kicks in and that Fill is not used.

This is what we get from the xaml above … but basically … success! :-)

HOnImageColorBurn600Success

Now, a few caveats. :-( This whole thing seems to be very fragile.

For example, in order to get this to work, I had to put the image inside the grid … as a sibling to the Canvas that contains the ‘H’ path. Certain other ways of doing it didn’t work. For example, if I set the BackgroundEffectBehavior’s Visual to the grid which contains both the grid/image and the ‘H’ path … it doesn’t work.

Also, if you change the StaticResource(s) to DynamicResource(s) … it doesn’t work.

Another issue is that using this behavior inside of Blend … does not result in what-you-see-is-what-you-get (WYSIWYG) blending. That is why I also set the Fill of the Path above to the same SolidColorBrush that I used as my ‘B’ input to the color burn blend mode effect. In that way, at least, I can see the ‘H’ inside of Blend.

Finally, I have not done any performance analysis (yet) of this, but the BackgroundEffectBehavior uses two VisualBrush(es) to get its job done. So, if you use a lot of these, I wouldn’t be surprised if it starts slowing stuff down.

The best solution here, is really for Microsoft to bake blend modes into the platform. Unfortunately, blend modes will not be in next release of WPF (.NET 4.x) … but the good news is that Brendan Clark from Microsoft says he’s pushing for it.

Here is the code. Unfortunately, the BackgroundEffectBehavior is WPF-only right now (I hope to fix this), but I have updated the Silverlight test harness to have the gradient and image test harnesses. Enjoy!

Here is a live Silverlight 3 test harness (you will need the Silverlight 3.0 runtime, 3.0.40624).

HelloWorldColorBurn600

p.s. I just want to give a shout-out to Mike Swanson and his wallpaper images. They rock. Period. The water droplet background I am using above is one of his.

Share/Save

Blend Modes, Pixel Shader Effects, WPF

  1. July 29th, 2009 at 20:42 | #1

    Thanks for the shout-out, Cory. Great article too!

  2. November 9th, 2009 at 00:38 | #2

    Hi Cory,

    I was wondering, could you make use of the WritableBitmap class to use as the effect’s Visual?

    I’m trying to get the ScreenEffect to work on a Video, and its tough! I’m running into the same issues that you fix here, although, Im trying to blend on a moving background… Any thoughts?

    Great series by the way :)

  3. cplotts
    November 9th, 2009 at 09:20 | #3

    @Mark

    Mark: you should be able to use any Visual … including WriteableBitmap. I do have a test harness that uses a blend mode (ColorBurn I think) on top of video. However, I strip that test harness out of the stuff I publish as the video is rather large in size.

    I will try to send that sample to you via email.

  1. November 5th, 2009 at 18:47 | #1
  2. November 7th, 2009 at 00:26 | #2
  3. September 20th, 2011 at 19:30 | #3