|Thursday, 31 December 2009|
Page 1 of 3
WriteableBitmap gives you a bitmap object that you can modify dynamically - but exactly how to do this isn't always obvious.
Bitmaps are generally implemented as immutable objects in WPF. What this means is that once you create a bitmap you can't make any changes to it. You can manipulate bitmaps by creating new versions, which then immediately become immutable and sometimes this is a good way to work. For more information on using immutable bitmaps see: BitmapSource: WPF Bitmaps
Immutable bitmaps can be very efficient unless you want to indulge in a lot of dynamic changes in which case the overhead of creating and destroying them rapidly becomes too expensive. In this situation you need something a little more flexible - the WriteableBitmap.
The WriteableBitmap, as its name suggests, isn't immutable and you can get at its individual pixels and manipulate them as much as you want. This is the ideal way to work when you need dynamic bitmaps. So let’s take a look at WriteableBitmap, how it works and how to use it to do dynamic things.
Notice that the WriteableBitmap class in Silverlight is very different to the same class in WPF.
To use WriteableBitmap we need to add:
which contains all of the additional bitmap facilities we need.
You can create a WriteableBitmap in two ways. The most commonly used is to simply specify the size and format of the bitmap:
WriteableBitmap wbmap = new
This specifies a WriteableBitmap 100 by 100 pixels with a resolution of 300dpi by 300 dpi using a Bgra32 pixel format. Each pixel, a 32-bit int, uses a four-byte BGRA – that is the pixel is made up of a byte giving the Blue, Green, Red and Alpha values. The final parameter is used to specify a palette if the format needs one.
You can specify a wide range of formats for the bitmap you create and in each case each pixel takes a given number of bits to represent and you need to find out how the bits allocated to each pixel determine its colour.
The second method of creating a WriteableBitmap is to base it on an existing BitmapSource or derived class. For example, you should be able to create a WriteableBitmap from a standard bitmap using the constructor:
but if you try this with a BitmapImage loaded from a URI you will get a null object error. The reason is that the bitmap might not yet be downloaded. For a local file the bitmap load blocks execution until it is loaded:
Uri uri = new Uri(@"pack://application:
In this case the WriteableBitmap is created with no problems. If the URI was an HTTP URL, however, the load would not block the execution and the result would be an error.
This makes it more difficult to use this constructor - see Loading Bitmaps: DoEvents and the closure pattern
Working with pixels
Once you have the WriteableBitmap you can start to work with its pixels. There are two methods provided which give you a limited equivalent of the BitBlt operation that you find in the low-level API. In this case you can either take a rectangle of pixels and copy them to an array or you can take an array and copy the data to a rectangle of pixels. In each case the array of data is treated as if it was a stream of bytes and simply stored or retrieved from the specified rectangle of pixels in the bitmap.
The WriteableBitmap understands the format of the bitmap it is storing and so it will automatically work out how many bytes are used per pixel and how to find the pixel data at a given co-ordinate. However, it can't know how you have organised the data in the array or how you might need the data organised when the pixels are read to the array.
The simplest scheme is that a row of pixel data is stored in the array adjacent to the previous row. That is, if the original image has p pixels and each pixel takes b bytes to represent then the first row in the array takes p*b bytes to store and the next row starts at p*b+1 and so on.
You can see that the pixel in column x row y (counting from zero) is stored at byte x*b+p*b*y. (This is an example of a storage mapping function.) The only complication with this simple scheme is that a single row of pixels might not end on a whole byte. For example, if you have a black and white image format you only need 1 bit per pixel and the first row of a 10 x 10 bitmap only needs 10 bits to store. However, to make things simple each row has to start on a new byte and so the amount of storage needed to store the first row is two bytes.
Notice that in this case the amount of storage allocated to a row is more than strictly needed according to the number of bits or bytes per pixel, i.e. it isn't just p*b.
There are other reasons why the storage needed for a row isn't always the obvious minimum - for example Windows insists that a row always begins on a 4-byte boundary.
For this reason we define and use the "stride".
The stride S is defined to be the amount of storage needed to store a row of the image including any padding needed to ensure that the next row starts correctly aligned.
In this case the WriteableBitmap takes care of the stride in its internal representation of the pixels and you don't need to worry about what value it is using. However you do have to worry about the stride used in the array of data that you are responsible for. In most cases you can simple set the stride to be the number of bytes needed to store a row of the bitmap - rounded up to make sure each row starts on a new byte if necessary.
You only have to worry about other issues, such as starting on a 4-byte boundary, if it is imposed by other parts of the system such as getting the data from a source that uses a specific stride.
|Last Updated ( Friday, 19 March 2010 )|