WriteableBitmap
Written by Administrator   
Thursday, 31 December 2009
Article Index
WriteableBitmap
A practical example
Extending WriteableBitmap

Banner

So much for theory - let's try it out.

 

Using the WriteableBitmap defined earlier we can easily create an array large enough to hold the entire 100 x 100 image:

 byte[] pixels = new byte[
wbmap.PixelHeight*wbmap.PixelWidth*
wbmap.Format.BitsPerPixel/8];

All the data used to create the array is obtained from the WriteableBitmap itself - including the number of bytes used to store each pixel. Notice that code given doesn't work is the number of bytes per pixel isn't a whole number. In this case stride = width * bytes per pixel.

To show that the whole thing works let's set the first pixel to blue:

   pixels[0] = 0xff;
pixels[1] = 0x00;
pixels[2] = 0x00;
pixels[3] = 0xff;

The order of the bytes is Blue, Green, Red and Alpha. Notice that for the pixel to show we have to remember to set the Alpha channel to 255 as the default value of zero makes the colour 100% transparent and hence it doesn't show.

Now that we have the array set up we can use the WritePixels method to write the data into the bitmap:

 wbmap.WritePixels(
new Int32Rect(0, 0,
wbmap.PixelWidth, wbmap.PixelHeight),
pixels,
wbmap.PixelWidth*wbmap.
Format.BitsPerPixel/8, 0);

Notice the use of a rectangle that covers the entire bitmap and the way that the stride in the array is specified. The final parameter is an offset in the array where the data starts. This is used to skip over header data or any other preambles in formatted pixel data. In this case the data starts immediately so the offset is zero. Again the stride is set to indicate the amount of storage used in the array for a single row of data.

CopyPixels

The same sort of technique can be used to transfer data from the WriteableBitmap to an array using the CopyPixels method. This works in a similar way to WritePixels only the direction of transfer is reversed. It is also worth noting that the CopyPixels method is inherited from BitmapSource and so is nothing new.

For example, to copy a block of pixels 20 wide by 10 high located at the top left-hand corner into an array you would use something like:

  byte[] pixels2 = new byte[10 *20 *4];
wbmap.CopyPixels(
new Int32Rect(0, 0, 20, 10),
pixels2, 20 * 4, 0);

Once again you need to create an array large enough to hold all of the pixel data and specify the rectangle in the bitmap that will be copied. The stride and the offset in the array need to be specified but this should be obvious after the previous example.

It is worth remembering that if you want to transfer all of the pixels in a bitmap to an array there is an overload which does exactly this:

  wbmap.CopyPixels(pixels, 100 * 4, 0);

In this case all of the pixel data is transferred into pixels using a stride of 400 and an offset of 0.

There are a number of overloads of both CopyPixels and WritePixels but they differ mainly in how the pixel data is provided - either as an array or as an IntPtr to an area of raw unmanaged memory. There are also overloads that allow you select a rectangle of pixels in the array or raw unmanaged memory to transfer to the bitmap. For example:

 wbmap.WritePixels(
new Int32Rect(0, 0, 10, 10),
pixels, 100 * 4, 0, 0);

This copies the pixels in the 10 x 10 square positioned at the top left-hand corner of the source array, i.e. pixels of the top left-hand corner of the bitmap starting at 0,0. Notice that the stride specified, i.e. 100 * 4, is the stride of the entire array regarded as a bitmap. The WritePixels method uses the stride you specify to work out where the pixels in the rectangle are located.

In practice you often need to manipulate the data in the array as if it was a bitmap in terms of co-ordinates and colour values. To do this you need suitable storage and colour mapping functions.

The storage mapping function is easy enough: 

  • the pixel at x,y is stored in x*b+s*y where s is the stride and b is the number of bytes per pixel.

The colour mapping function is more complicated and obviously depends on the pixel format selected.

Backbuffer access

The WritePixel and CopyPixel methods are fine if all you want to do is block modify a bitmap but if you want to change single pixels frequently block operations aren't the best way to go. There is an alternative but unfortunately this involves some unsafe code. Given the nature of the task the code isn't particularly unsafe and it really could have been implemented in a managed way - as is done in Silverlight's version of the WriteableBitmap.

The idea is that there is a backbuffer to which you can gain direct access via a pointer. The backbuffer is copied to a frontbuffer as and when it is necessary and this is achieved in a way that doesn't make the bitmap flash or tear during the update.

To access the backbuffer you simply make use of the BackBuffer property which returns an IntPtr with the address of the start of the backbuffer. The backbuffer is pinned in the heap and it won't move as long as you are using it - however it still seems safer to acquire the address at intervals. To stop the rendering thread from updating the frontbuffer while you are working on the backbuffer you have to lock the WriteableBitmap:

  wbmap.Lock();
IntPtr buff = wbmap.BackBuffer;

The easiest way to work with the IntPtr is to convert it to a pointer and specifically a pointer to a byte makes it possible to treat the pointer as if it was a byte array. This section of code is unsafe, as is any use of pointers, so it has to be wrapped in an unsafe statement. After converting the IntPtr to a pointer to void we can cast it to a pointer to byte:

unsafe
{
byte* pbuff = (byte*)buff.ToPointer();

From this point on we can treat pbuff as if it was an array because C# automatically dereferences a pointer index. For example, pbuf[1] is equivalent to writing *(pbuf+1). Setting the first pixel to blue can be done in the same way as the earlier example:

  pbuff[0] = 0xff;
pbuff[1] = 0x00;
pbuff[2] = 0x00;
pbuff[3] = 0xff;
 }

and this closes the unsafe clause because we are not going to be using the pointer again. All that remains is to mark an area of the backbuffer as "dirty" so that the rendering system knows to copy those pixels to the frontbuffer for display and unlock the WriteableBitmap. If you don't mark the backbuffer as dirty then the update isn't performed and you don't see the change:

 wbmap.AddDirtyRect(
new Int32Rect(0,0,10,10));
wbmap.Unlock();

The only disadvantage of this method of working is that it involves the use of unsafe code.

<ASIN:0672328917>

<ASIN:0672330334>

<ASIN:1590599551>



Last Updated ( Friday, 19 March 2010 )