In my last post I demonstrated a way to simulate Photoshop’s angle gradient in Silverlight using Effects (or “Pixel Shaders”, to use the technical term). Pixel Shaders are an addition that can add dramatic capabilities to your Silverlight application but it also has its serious cons. It’s hard to debug, hard to maintain, and it can affect your application’s performance badly.

One of the most misunderstood features of Silverlight is the drop shadow effect. We all like to add a little touches of 3D into our user interface. The problem is that the less known “side effects” of this effect can affect your application badly. In this post I will try to give you 2 motivations to “drop” the “DropShadow” effect and replace it with an alternative trick. The first motivation (The Stick) is the substantial performance hit your application may suffer, especially if you miss-use this feature. The second motivation (The caret) is the cool shadow effects you can achieve with the alternative trick.

The Stick – DropShadow’s performance issues

Here is a common example of how misuse of pixel shaders can be a disaster. Assume we want to build the following user control:

 

Here is the XAML that created this:

 

It’s a simple border with a 3*2 Grid. The border has a gradient fill, and border brush, and a drop shadow effect.

So what’s the problem with this code?

To understand the problem, we need to understand how the “Effect” property works. This property applies a pixel shader on the element that it is set to. A pixel shader is a small piece of code written in a special language called HLSL, whose syntax is very close to C++. This piece of code contains a function that is executed once for every pixel that is about to be displayed in order to ask it which color to use in that pixel, so in a way, this function is like a “last minute change to the graphics about to be drawn”. The function takes a pair of coordinates, and can use several “global” variables that contain things like the original bitmap that was about to be drawn, and perhaps some other arguments, and after doing it’s calculations, it outputs the color to use for that specific pixel.

Silverlight comes with two built in effects: Blur and DropShadow. As you have seen in my previous post, you can easily add your own effects. The built-in blur effect blurs the original bitmap by using an algorithm that returns for each pixel a color that is a “smart” average of the colors of the pixels around it. DropShadow is more sophisticated. I am not going to get into the exact algorithm but its obvious that it does not modify any of the “opaque” pixels, but where there are transparent pixel that are close to opaque pixels, it draws a black pixel in one or another level of opacity.

 

The most important thing to remember is this:

The algorithm does not care about the color of each original pixel, only for their opacity. As long as the opacity of the input bitmap does not change, the output will stay the same. But that does not mean that Silverlight does not re-calculate it. Since effects CAN depend on actual color of the pixels, and since Silverlight does not know exactly what the effect does with the original graphics, it assumes that every change in the input can affect the output. So every time there is a change in the content of the control, the shader needs to be recalculated. Since border is a content control, that means that the shadow is recalculated on every modification that is made to its content. A mouse over, scroll, blink of the cursor in the text boxes, anything.

Think of your Silverlight application as some kind of movie that is generated on the spot. There is a logic model behind (the XAML is translated into an hierarchy of objects called “the visual tree” that is rendered into frames of the “movie”). In order for the movie to have a fluid motion, Silverlight needs to be able to render tens of frames every second (at least 20, preferably over 50). If the rendering procedure becomes too costly, Silverlight does not have time to calculate enough frames per second and the effect is that our application “movie” starts to flicker. Placing effects on content controls is a sure way to reduce the frame rate to a number that the user will not feel comfortable with.

So what is the correct way to do it?

One way will be split the border into 2 borders and place them one on top of the other. The “top” one will contain the content, and the bottom one will serve as background. We can apply the drop shadow effect on the background, and this ensures us that modifications to the content will not cause recalculations of the shadow.

But still, drop shadow is a relatively expensive effect. The blur effect is a lot less expensive, especially when it is applied to basic, non-control, certainly-non-content-control, shapes. I would get the same effect as follows:

 

So I have wrapped the original border with a grid and set the layout properties (width, alignment etc) on the grid. Next I added rectangle with the same shape as the border (same corner radius and full size, just like the border). Since it appears before the border in the XAML, it will appear “behind” the border on the screen. The rectangle is black, (so we have a black shadow). I applied a blur effect on it, and a render transform so that it has a small offset compared to the border itself.

Since the rectangle and the border both fill the entire area of the grid, you know for sure that they will overlap. The rectangle does not have a size of its own, so it will consume exactly the same area that the border will, so if you modify the border’s content, the rectangle will adjust itself to the same size.

But what about all the parameters that the real shadow effect has?

Now, just to show you that you did not lose any functionality that the original drop shadow has, lets look at all the parameters the original DropShadow effect has, and show you how to get the same results with this simple structure:

Usually a drop shadow effect is defined by 4 parameters:

1. The “color” – this can be controlled using the “Fill” property of the rectangle

2. The “size” – this is the blur radius. So you can set the Radius property of the blur effect.

3. The “distance” – this can be manipulated by modifying the TranslateX and TranslateY properties of the transformation matrix.

4. The “opacity” – this can be manipulated either by setting an alpha channel to the Fill property, or by setting the Opacity of the rectangle.

The Caret – The cool shadows you can create

This technique does not only allow us to improve the performance while maintaining the original effect’s functionality, it also allows us to expand it’s functionality and achieve some crazy shadows effects. We can use a gradient instead of a solid brush to fill the rectangle and get a shadow that changes color. We can use other transformations in addition to “translation” and get the shadow to have a different shape.

Here is an example of what we can acomplish:

 

We have added a “SkewX” and “SkewY” transformations to the shadow, and also used a gradient from black to semi-transparent black. Here is the result:

 

Here is another example, this time we moved the transformation center to the bottom right corner, and used SkewX and ScaleY transformations:

 

To achieve the following shadow:

 

The most important thing to remember is this:

We never set a size to the rectangle that is used as a shadow, or a margin. So if you add content to the border, that causes it to grow, the shadow will always go along with it, as shadows usually do... It will keep its relative transformations but they will always be loyal to the shape of the border it lies behind.

What about reusability?

One approach to making this technique more “reusable” is to create a user control for each shadow you would like to reuse. You can even expose some properties such as the fill brush, or blur radius. This way, you don’t have to copy-paste the entire rectangle. Another possibility is to simply define a style and set all the properties there. Or, you can define a custom control, give it some dependency properties, and create a template that will generate the shadow you want while  template binding to the controls properties. All these methods are “kosher” and they all depend on the level of reuse and flexibility you are trying to achieve.

Cheers,