Click here to Skip to main content
Click here to Skip to main content

Creating Inner Shadow Effects for WPF and Silverlight

, 13 Jul 2011
Rate this:
Please Sign up or sign in to vote.
A few tricks for creating Photoshop-style Inner Shadow effects in WPF and Silverlight.

Introduction

If you've ever been tasked with converting a Photoshop design into a WPF UI, you'll probably have tried the Expression Blend Photoshop Import feature. So you'll know that whilst it does a pretty good job of importing simple Photoshop files, it struggles when asked to convert the little flourishes with which designers like to top off their masterpieces.

Like Inner shadows.

In this article, I'll show you a couple of ways of creating inner shadow effects in WPF, one of which also works for Silverlight.

Pleased to meet you, Mr. Inner Shadow

If you've not met an inner shadow before, allow me to introduce you.

Here’s a grey rectangle:

image

And here's a grey rectangle with an inner shadow:

image

Subtle, isn’t it? But it adds a touch of realism to the rendering.

Clip Regions and Opacity Masks

Question is, how can we create an inner shadow in WPF? Look in the System.Windows.Media.Effects namespace, and you’ll find the DropShadowEffect class, but no InnerShadowEffect. Don’t let that fool you. DropShadowEffect can be used to create an inner shadow, using one of two nifty WPF features: Clip regions and Opacity Masks.

Clip regions and Opacity Masks both achieve a similar effect, though in different ways. They instruct WPF to trim off, or hide, certain parts of a visual when rendering it to the screen. Clip regions are geometries – rectangles, ellipses, or arbitrary paths, which specify the boundaries of a shape. Everything inside the geometry is rendered, everything outside ignored. They tell WPF, thus far shalt thou render, and no further.

Opacity Masks are like stencils which WPF lays over the top of your element. Each pixel in your element is given the same opacity as the corresponding pixel in the Opacity Mask. If the pixel in the Opacity Mask is transparent, then WPF ignores the corresponding pixel of your element. If the pixel in the Opacity Mask is opaque, or semi-opaque, then WPF renders that pixel of your element.

Inner Shadows Using ClipToBounds

I’ll start with the simplest technique for drawing Inner Shadows: applying Clip regions using the ClipToBounds property. Set this to true, and WPF will make sure that no part of an element or its children will spill outside of its borders. This is the easiest way of using Clip regions because you don’t have to think about their geometry or worry about resizing if the element changes size: WPF uses the natural bounding-rectangle of the element as the Clip region.

Here’s how I created the inner shadow shown above:

<Border Background="LightGray" BorderBrush="DarkGray" 
           BorderThickness="1" ClipToBounds="True">
  <Border Background="Transparent" BorderBrush="Black" 
              BorderThickness="1" Margin="-2">
    <Border.Effect>
      <DropShadowEffect ShadowDepth="0" BlurRadius="10">
    </Border.Effect>
  </Border>
</Border>

There are several things to notice here:

  1. I apply the DropShadowEffect to an element (a Border in this case) that has a solid border, but transparent fill. This produces a shadow effect on both sides of the border.
  2. The DropShadowEffect has its ShadowDepth set to 0 so that there’s no gap between the border and the start of the shadow. This also ensures that the shadow is even in all directions. Fiddle with DropShadowEffect’s BlurRadius and Opacity properties if you want to vary the lightness of the shadow. There’s the Color property to experiment with too.
  3. The critical part that makes this shadow an inner, and not an all-round, shadow: the Border with the DropShadowEffect is nested inside another border which has ClipToBounds="True". This is what clips off the outer part of the drop shadow.
  4. I use negative margins on the inner border to push out its edge so that it gets clipped by the Clip region of the outer border, effectively hiding it.

Another thing you can experiment with is changing the thickness of the inner border. This lets you vary the density of the inner shadow. For example, a BorderThickness of 10 (and a Margin of –12 to compensate) gives you:

image

Want shadows on just a couple of edges? That’s easy: just turn off the appropriate lines on the inner Border using its BorderThickness property.

Here’s a rectangle with an inner shadow on just its top edge:

image

And here’s the markup:

<Border Background="LightGray" BorderBrush="DarkGray" 
      BorderThickness="1" ClipToBounds="True" Width="400" Height="100">
  <Border Background="Transparent" BorderBrush="Black" 
        BorderThickness="0,10,0,0" Margin="0,-11,0,0">
    <Border.Effect>
      <DropShadowEffect ShadowDepth="0" BlurRadius="10"/>
    </Border.Effect>
  </Border>
</Border>

Sadly, this easy-to-use method won’t work in Silverlight, because it doesn’t have the ClipToBounds property. But don’t lose heart. I’ve another trick up my sleeve.

Inner Shadows with the Clip Property

A technique that works as well in Silverlight as WPF is using the Clip property to set the geometry of the Clip region. Here’s an example:

<Grid>  
  <Rectangle Width="400" Height="100" Fill="LightYellow" 
      Stroke="Orange" StrokeThickness="2" RadiusX="8" RadiusY="8"/>
  <Rectangle Width="400" Height="100" Fill="Transparent" 
        Stroke="Orange" StrokeThickness="2" RadiusX="8" RadiusY="8">
    <Rectangle.Effect>
      <DropShadowEffect ShadowDepth="0" BlurRadius="15" Color="Orange"/>
    </Rectangle.Effect>
    <Rectangle.Clip>
      <RectangleGeometry Rect="0,0,400,100" RadiusX="8" RadiusY="8"/>
    </Rectangle.Clip>
  </Rectangle>
</Grid>

which looks like this when rendered:

image

As you can see, I’m using the same trick of applying the DropShadowEffect to a shape with a solid border but transparent fill, then clipping off the outer shadow, this time by setting a Clip region on the same shape. To get the background fill, I put another copy of the shape behind (using a Grid to layer the two) and set its Fill to the colour I want.

The thing that makes this not quite so easy to use as the ClipToBounds method is that the geometry in the Clip property has to match exactly the shape of the element you’re applying it to. If the element changes in size, the geometry has to be updated too – and WPF won’t do that automatically. So this technique might work quite well in code, but not so straightforwardly in XAML, unless you have simple, static shapes.

One tip if you do go down this route: Expression Blend has good support for applying Clipping Paths to elements.

Inner Shadows using Opacity Masks

The final technique I want to show you is drawing Inner Shadows using Opacity Masks. This is what we’re shooting for:

image

And here’s how to make that in XAML:

<Grid Width="400" Height="200">
  <Grid.OpacityMask>
    <VisualBrush Visual="{Binding ElementName=Shape}" Stretch="None" />    
  </Grid.OpacityMask>    
  <Path x:Name="Shape" 
        Data="M98.765432,136.43836 L180.41152,1.6438356 262.05761,103.56164 343.7037,
              21.369863 470.12346,126.57534 596.54321,54.246575 551.76955,231.78082 638.68313,
              340.27397 528.06584,353.42466 525.4321,478.35616 391.11111,373.15068 285.76132,
              461.91781 217.28395,350.13699 135.63786,475.06849 114.5679,346.84932 1.3168724,
              251.50685 140.90535,212.05479 z" 
        Fill="#FFF8F93F" Stroke="#FFAB6600" Stretch="Fill" />
  <Path Data="M100.57143,137.83784 L181.55102,4.8648649 262.53061,105.40541 343.5102,
              24.324324 468.89796,128.10811 594.28571,56.756757 549.87755,231.89189 636.08163,
              338.91892 526.36735,351.89189 523.7551,475.13514 390.53061,371.35135 286.04082,
              458.91892 218.12245,348.64865 137.14286,471.89189 116.2449,345.40541 3.9183673,
              251.35135 142.36735,212.43243 z" 
         Stroke="#FFAB6600" StrokeThickness="3" Stretch="Fill" >
    <Path.Effect>            
      <DropShadowEffect ShadowDepth="0" BlurRadius="20" Color="Orange"/>        
    </Path.Effect>    
  </Path>
</Grid>

Again, we’ve got two copies of the shape layered in a Grid, the first one to give the background colour, the one on top to create the shadow effect.

The difference this time is that we’ve set the Grid’s OpacityMask property. We’re taking advantage of WPF’s VisualBrush to create the mask from the shape with the solid fill. This means that when the Grid and its contents are rendered and the Opacity Mask made from the solid shape applied, pixels outside of that base shape will not be rendered, thus neatly clipping off the outer part of the shadow created by the top shape. Note that it is important to set Stretch=”None” on the VisualBrush to get exact alignment of the mask with the shapes it is masking.

The nice thing about this technique is that the Grid will take care of resizing everything for us, which makes it much easier to use than setting Clipping regions. One downside is that performance is not going to be quite so good, since using Opacity Masks and VisualBrushes will require WPF to create Intermediate Render Targets where it draws the elements first before compositing them with the rest of the scene. This could be offset to some extent by the judicious use of Bitmap Caching.

Silverlight and OpacityMasks

Whilst Silverlight does support Opacity Masks, it does not support VisualBrushes. So unfortunately, that rules out use of this last technique for Silverlight.

Acknowledgements

I can’t claim to be the first to think of these techniques. Inspiration came from Timo Pijnappel and another post which depleted Google Foo prevents me from finding right now. Let me know if you think it might be yours!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Samuel Jack
Technical Lead Seaturtle Software Ltd
United Kingdom United Kingdom
I'm a freelance software developer based in the UK. My portfolio ranges from music video editing software to applications used to oversee clean-up operations at a nuclear site.
 
I've worked with most layers in the Microsoft .Net stack, but I have a particular love for WPF.
 
Read more on my blog or in my CV.
Follow on   Twitter

Comments and Discussions

 
GeneralGreat Post! PinmemberDiamonddrake2-May-14 7:19 
QuestionInner shadow for fonts PinmemberMember 982186125-Feb-13 7:32 
AnswerRe: Inner shadow for fonts PinmemberSamuel Jack1-Mar-13 8:35 
GeneralMy vote of 5 Pinmemberraj23karthik18-Dec-12 0:06 
GeneralMy vote of 5 PinmemberSolarCell6-Nov-12 15:01 
QuestionAwesome! PinmemberReno Tiko11-Sep-12 14:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 13 Jul 2011
Article Copyright 2011 by Samuel Jack
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid