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:
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:
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):
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:
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:
Using FormatConvertedBitmap is certainly easier (but a lot less flexible) in these scenarios.