The use of vector graphics is really powerful because it connect the low weight for big images and free scalability to every size. Nevertheless sometimes you need to return to raster images because they grant a more contro to the rendering pixel by pixel and for photographic rendering are better. With Silverlight 2.0 the only way to use raster images is to download the content from the server, and embed it into an element <image> on the UserControl.
With Silverlight 3.0 a new way to manage raster images has been added. Using a WriteableBitmap it is possible to generate images on the fly. This tecnique is really powerful and someway unite the best of both vector graphic and raster images. Using this class you may render big images withoud downloading them from the source server.
Using WriteableBitmap: Back to Mandelbrot
To give an example of the great power of image generation let me explain how to create a simple silverlight application to generate Mandelbrot fractals on the fly. The core of the application is a short algorithm, downloaded from CodeProject that render a Mandelbrot set into a bitmap.
A Mandelbrot set is one of many set of fractals, probably the most famous, explored for the first by Benoît Mandelbrot. The Fractal geometry is out of the scope of this article so let me skip a deep explanation of the fractals theory. If you need you may find many informations here.
The only thing we need to know here is that a Mandelbrot Fractals may be zoomed and panned like a map and this generate a large set of wonderful image. You may see an example in the side image that show also the interface of the application I'm going to explain.
The first problem we have to address is the time needed to generate the image. This may be a time consuming activity but also may consume a lot of resources. In a silverlight application this may become to a freeze of the browser during the rendering. To avoid this problem we have to use a background thread that calculate every single pixel and write it to the raster image. Another little problem is that the WriteableImage is a UI element and it is not possible to write inside of it in a separated thread. So we have to calculate all the pixels in a System.Int32 array and at the end of the elaboration we switch to the UI thread and copy the array to the WritableBitmap. Here is the code to render the image:
1: // Declare a buffer to generate the image
2: int bmap = new int[this.Width * this.Height];
4: // Calculate here the Mandelbrot image into the int .
5: // ...
7: // Mashal to the UI Thread before render
9: new Action<int>(
10: data =>
12: // create the bitmap
13: WriteableBitmap output = new WriteableBitmap(this.Width, this.Height, PixelFormats.Pbgra32);
19: for (int i = 0; i < data.Length; i++)
20: output[i] = data[i];
22: // do something with generated image
30: }), bmap);
From this short snippet we may learn a bunch or things: first of all writing a raster image has to be made pixel-by-pixel like we have to fill and array. The data is stored from the top line to the last line and from the left to the right. So at the position zero we have the top left pixel, then we continue with the first line and after the end we begin the second line and so on. This is a simple conversion from X and Y using Width of the image as modulus:
int pixelIndex = Y * Width + X;
In the constructor of the WriteableBitmap we may chose the PixelFormat for our pixels. There is only to values possible. Bgr32 specify a RGB color and Pbgra32 specify a ARGB where A stnd for Alpha, the usual color format for Silverlight.
The call to the methods Lock and Unlock are required to access to the pixels. This methods grant the exclusive access to the bitmap and lock the rendering from inside another thread.
The resulting image may be used directly in a ImageSource Property to show the generated image in the interface.
The sample application
The application is sligthly simple. We have an initial rectangle from -2.1 to 1.0 horizontally and from -1.3 to 1.3 vertically. These are the default "coordinates" that show the full Mandelbrot set as you have probably already saw. We have a method Redraw() that accepts the UI coordinated of the part to render. The initial value is the full size of the canvas but wen we select a part of the image or click one of the panning and zooming buttons the square is recalculated and the part of the set become the full image. In thes way we can zoom to the most little particular of the set and see the fractas in all his beauty.
The generation of the image is done by a special class that incapsulate the algorithm and the thread marshaling. The only thing we get by this class called MandelbrotGenerator is the Completed event containing the generated image.
Here is a full size screenshot of the application:
Some other words about WriteableBitmap
One thing I've missed speaking about this class in that the WritableBitmap can render a part of the VisualTree into an image. This may be useful to create effects like mirroring where all or part of an UIElement is rendered rotated on a bitmap and showed on the bottom of the original element to simulate a reflection effect. Unfortunately the Render() method does not work correctly in this beta. It is affected by the position of the elements in the Visual Tree so you have to surround the elements to render by a Border and sometimes this do not work (or at least I cannot get it working). So I will post a new article when this object will be fully working in the future releases.
Download Code: Elite.Silverlight3.Fractals.zip (867 KB)
Demo Video: WriteableBitmap.wmv (3.5 MB)