WPF Tip: Displaying Images in different Pixel formats

January 30, 2012

5 comments

If we want to show an image in WPF, we typically use an Image element and connect its Source property to some image resource within our project:

  1. <Image Source="Penguins.jpg" />

The Source property is not a string, it’s an ImageSource – an abstract type with several concrete implementations that provide a “real” image source. The above markup works thanks to the help of a type converter, that makes the source a BitmapImage – one of the simplest sources, that presents the image as is.

What if we wanted to show the original image in a different pixel format, such as a gray scale image?

The typical (and powerful) approach would be to use a pixel shader. What is a pixel shader? Briefly, it’s a little program that executes on the graphic card (GPU), that can transform an incoming pixel to a different color, based on some algorithm. There are other kinds of shaders, but we won’t discuss those here, as they are not relevant to WPF.

These shaders are not written in C#, mind you – I wish that would be possible (maybe in the future). They are typically written in a Domain Specific Language (DSL) called High Level Shader Language (HLSL), that was created for DirectX (and used by XNA as well). This language syntax is based on C, with added features relevant to shaders. Writing shaders is well beyond the scope of this post, but let’s see a simple gray scale shader at work:

  1. sampler2D implicitInput : register(s0);
  2.  
  3. float4 MainPS(float2 uv : TEXCOORD) : COLOR
  4. {
  5.     float4 src = tex2D(implicitInput, uv);
  6.  
  7.     float4 dst;
  8.     dst.rgb = dot(src.rgb, float3(0.3, 0.59, 0.11));
  9.     dst.a = src.a;
  10.     
  11.     return dst;
  12. }

What this basically says, is that given an input 2D coordinate (uv), we need to return a COLOR for that coordinate. The color (dst) is comprised of a dot product between the source color (src.rgb) and a weighted vector, that is typically used to convert to gray scale (0.3, 0.59, 0.11). The computation (in an easier form, removing the “dot” notation) is:

gray level = red * 0.3 + green * 0.59 + blue * 0.11

This value is fed into the destination R,G and B components (when R=G=B we see gray), and the alpha component is copied as is.

What do we do with this file? Typically this is saved with a “.fx” extension, and then it needs to be compiled into binary form, suitable for execution on the GPU using the effects compiler (fxc.exe) that is part of the DirectX SDK. The command line for this looks something like this:

fxc /T ps_2_0 /E MainPS /Fo gray.ps gray.fx

This says that we want to use pixel shader level 2.0 (there are others, an fxc.exe also compiles other shaders, such as vertex shaders), the main function is called MainPS and our output should be gray.ps. The input is the last argument, gray.fx.

In the sample project, I’ve already compiled the file. The result is typically named with a “.ps” extension. This PS file is then added as a resource to the project.

We’re not done, yet…

The next thing to do is create a wrapper class, deriving from ShaderEffect, that loads the shader and initializes it. This new class can then be used as the value of the UIElement.Effect property. Here’s what the class looks like:

  1. class GrayscaleEffect : ShaderEffect {
  2.     public GrayscaleEffect() {
  3.         PixelShader = new PixelShader();
  4.         PixelShader.UriSource = new Uri("/Effects/Gray.ps", UriKind.Relative);
  5.         UpdateShaderValue(InputProperty);
  6.     }
  7.  
  8.     public static readonly DependencyProperty InputProperty =
  9.         ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(GrayscaleEffect), 0);
  10.  
  11.     public Brush Input {
  12.         get { return (Brush)GetValue(InputProperty); }
  13.         set { SetValue(InputProperty, value); }
  14.     }
  15. }

I will not get into a full explanation regarding the Input property and its use here. The pixel shader is initialized from the PS file resource. Now we can apply it to any element (not just an image):

  1. <Image Source="Penguins.jpg">
  2.     <Image.Effect>
  3.         <local:GrayscaleEffect />
  4.     </Image.Effect>
  5. </Image>

This works well (and actually works in Silverlight as well), but for a gray scale effect, there is an easier way in WPF: use the FormatConvertedBitmap class as the ImageSource and set the desired pixel format (using the DestinationFormat property) – we can select one from the PixelFormats class that exposes a set of static properties as PixelFormat objects.

Here’s a roughly equivalent way to get a gray scale image:

  1. <Image>
  2.     <Image.Source>
  3.         <FormatConvertedBitmap Source="Penguins.jpg" DestinationFormat="Gray8" />
  4.     </Image.Source>
  5. </Image>

The attached sample shows the penguins image in four ways: the original, with the gray scale shader, with a black & white pixel format and a gray8 pixel format as above. This is the result:

image

Using FormatConvertedBitmap is certainly easier (but a lot less flexible) in these scenarios.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

5 comments

  1. RadomskiyJune 22, 2012 ב 13:00

    Hey this might seem random, trust me it is haha My name’s Matt, I am a 23 year old miscuian and I have my own home studio where I produce and record bands including my own and many many others but more importantly, I do alot of rap beats, my own dance project, music for an indie film, and now want to get into doing XNA games.I left Kyle Schouviller’s (also a random find) blog basically the same message- just that I’m not really looking for money, but if you are in need of music for XNA games or know someone who needs it, please have them contact me. There are several samples of my creative and other capabilities on my website (I’ll have a non-myspace website soon i hope ;o) haha) and can send some other examples that I have that are more appropriate for video games.Thanks!

    Reply
  2. HodgsonJuly 27, 2012 ב 09:42

    Really good article. Thumbs up!

    Reply
  3. �륤����ȥ� �Хå� ���� 2013August 17, 2013 ב 09:02

    Various tenants outside of the Doyle Gardens Flats mentioned mattress bug chunk scars stay for quite a while following the itching wears off.

    Reply
  4. Hermes BirkinAugust 19, 2013 ב 10:43

    The phonebook for the Nokia X101 isn’t as ultra powerful given that the just one on S40 versions, but you still get a sufficient amount of operation.

    Reply