WriteableBitmap in Silverlight
Written by Ian Elliot   
Monday, 09 August 2010
Article Index
WriteableBitmap in Silverlight
Creating WriteableBitmaps

In the main bitmap graphics in Silverlight are second class citizens because Silverlight is based on WPF which emphasises vector graphics. However, in practice bitmaps in Silverlight are very important for reasons of efficiency since Silverlight doesn't have access to the GPU. Find out how to work with dynamic bitmaps.

Banner

 

In the main bitmap graphics in Silverlight are a second class citizens because Silverlight is based on WPF which emphasises vector graphics. However in practice bitmaps in Silverlight are very important. The reason is that while WPF can get away with what at first sight appear to be inefficient implementations of graphical methods it gets away with it because it has access to graphics hardware acceleration.

Silverlight for reasons of security ignores the GPU and so has no hardware graphics acceleration and as such occasionally you need to use bitmaps, and dynamically generated bitmaps, to make things efficient.

The good news Silverlight can do dynamic bitmaps with the  WriteableBitmap object introduced in Silverlight 3. It can even do bitmap manipulation which opens up a whole new range of possibilities by way of dynamic bitmaps generated by code rather than loaded from a URI.

The only problem is that the facilities provided within Silverlight aren’t as complete as the full WPF implementation. On the other hand there are aspects of Silverlight bitmaps that are arguably better than their WPF analogs. What is important however is that you know what the Silverlight bitmap objects will do and how they differ from the WPF implementation of supposedly the same objects. 

So let’s take a look at WriteableBitmap, how it works and how to use it to do dynamic things.

WriteableBitmap basics

To use WriteableBitmap we need to add:

using System.Windows.Media.Imaging;

which allows us to reference all of the bitmap facilities we need without having to write them out in full.

You can create a WriteableBitmap in three ways. The most commonly used is to simply specify the size of the bitmap:
WriteableBitmap wbmap = 
new WriteableBitmap(100, 100);

 

Notice that this constructor isn’t the same as the full WPF constructor, which includes pixel formats and other specifies. The reason is that a Silverlight bitmap has a fixed pixel format. Each pixel, a 32-bit int, uses a four-byte ARGB – that is the high byte is the alpha channel followed by bytes for the Red, Green and Blue channels.

Once created you can access the individual pixels using the Pixels property. The only problem is that they are stored in a one-dimensional array without any differentiation into row or column. In fact they are stored in row order starting with the top left-hand pixel.

Each pixel is stored as a 32 bit int and the color data is packed into the four bytes as already explained. This has to be contrasted to WPFs way of doing the same job. The WPF WriteableBitmap uses a byte array with four bytes per pixel. Not only is the format different but the WPF WriteableBitmap needs an external byte array which is constructed or manipulated and then transferred as a block to the bitmap's pixels.

To make use of the Pixels property we have to construct a storage mapping function to map the x and y co-ordinates into the one-dimensional array.

That is, the pixel at x,y is stored in Pixels[x+w*y] where w is the width of the bitmap in pixels.

It’s a mystery why WriteableBitmap doesn’t have any methods that implement this mapping but it doesn’t. It is easy enough to add however by way of two extension methods. First we need a static class that can be used as the container for the extension methods:

public static class bitmapextensions
{

The setPixel method first checks that the x and y co-ordinates are in the correct range:

public static void setPixel(
this WriteableBitmap wbm,
int x, int y, Color c)
{
if (y > wbm.PixelHeight - 1 ||
x > wbm.PixelWidth - 1) return;
if (y < 0 || x < 0) return;

Then it computes the storage mapping function to access the pixel at that x,y location and stores the specified colour:

wbm.Pixels[y * wbm.PixelWidth + x] = 
c.A << 24 | c.R << 16 |
 c.G << 8 | c.B;
}

The only complication is the use of left shift operator << to place the bytes in the correct position within the 32 bit integer.


The getPixel method is very similar, only it assembles and returns a Color struct:

 public static Color getPixel(
this WriteableBitmap wbm, int x, int y)
{
if (y > wbm.PixelHeight - 1 ||
 x > wbm.PixelWidth - 1)
return  Color.FromArgb(0,0,0,0);
if (y < 0 || x < 0)
return Color.FromArgb(0,0,0,0);
byte[] ARGB=BitConverter.GetBytes(
wbm.Pixels[y * wbm.PixelWidth + x]);
return Color.FromArgb(
ARGB[3], ARGB[2], ARGB[1], ARGB[0]);
}
}

Notice the use of the BitConverter object to convert the four bytes that make up the Int32 pixel value to a four-byte array. BitConverter is a much overlooked object which provides a link between high level data types and their representations in bits.

These two extension methods are easy to use but you might want to think about using more direct methods if you are going to manipulate a lot of pixels. Direct access via the Pixel property, and assigning pre-computed integer colour values, is always going to be faster than using general methods and objects.

Banner

The complete class is:

public static class bitmapextensions
{
public static void setPixel(
this WriteableBitmap wbm,
int x, int y, Color c)
{
if (y > wbm.PixelHeight - 1 ||
x > wbm.PixelWidth - 1) return;
if (y < 0 || x < 0) return;
wbm.Pixels[y * wbm.PixelWidth + x] =
 c.A << 24 | c.R << 16 |
c.G << 8 | c.B;
}

public static Color getPixel(
this WriteableBitmap wbm, int x, int y)
{
if (y > wbm.PixelHeight - 1 ||
x > wbm.PixelWidth - 1)
return Color.FromArgb(0, 0, 0, 0);
if (y < 0 || x < 0)
return Color.FromArgb(0, 0, 0, 0);
byte[] ARGB = BitConverter.GetBytes(
wbm.Pixels[y * wbm.PixelWidth + x]);
return Color.FromArgb(
ARGB[3], ARGB[2], ARGB[1], ARGB[0]);
}
}

<ASIN:0470534044>

<ASIN:1430224258 >

<ASIN:0672330628 >

<ASIN:0470524650 >

<ASIN:1430229888 >



Last Updated ( Monday, 30 August 2010 )