A stable and fast line-drawing algorithm is the basis for most shapes like triangles, rectangles or polylines in general. For this blog post I've extended the WriteableBitmap with some specialized methods for various shapes including a fast ellipse rasterization algorithm.
Live
The application includes various scenarios where different shapes are drawn. By default a little demo is shown that I call "Breathing Flower". Basically different sized circles rotating around a center ring are generated. The animation is done using the best trigonometric functions in the world: sine and cosine.
The scenario "Static: WriteableBitmap Draw* Shapes" presents all shape extensions currently available. From left to right: Points - SetPixel(), Line - DrawLine(), Triangle - DrawTriangle(), Quad - DrawQuad(), Rectangle - DrawRectangle(), Polyline - DrawPolyline(), closed Polyline - DrawPolyline(), Ellipse - DrawEllipse(), Circle - DrawEllipseCentered().
The other two scenes randomly draw all shapes or only ellipses and allow controlling the work load by setting the number of shapes. The Silverlight frame rate counter at the upper left side shows the current FPS in the left-most column.
How it works
Most of the new extension methods use the DrawLine() function to build up a shape. Only the DrawRectangle() method implements a simplified line drawing using some for loops which is faster than calling the DrawLine() method four times. The DrawEllipse() function implements a generalized form of the Midpoint circle algorithm. I've used "A Fast Bresenham Type Algorithm For Drawing Ellipses" from this paper by John Kennedy.
The extension methods are pretty fast and if you need to draw a lot of shapes and you don't need anti-aliasing, Brushes or other advanced UIELement properties, the WriteableBitmap and the Draw*() extensions methods are the right choice. If you don't like the sharp edges, you can apply the Silverlight 3 Blur effect to the image:
The signature of the extension methods
DrawPolyline(this WriteableBitmap bmp, int[] points, Color color);
DrawPolyline(this WriteableBitmap bmp, int[] points, int color);
DrawTriangle(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int x3, int y3, Color color);
DrawTriangle(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int x3, int y3, int color);
DrawQuad(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, Color color);
DrawQuad(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int color);
DrawRectangle(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, Color color);
DrawRectangle(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int color);
DrawEllipse(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, Color color);
DrawEllipse(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int color);
DrawEllipseCentered(this WriteableBitmap bmp,
int xc, int yc, int xr, int yr, Color color);
DrawEllipseCentered(this WriteableBitmap bmp,
int xc, int yc, int xr, int yr, int color);
The DrawPolyline() method uses an array of x- and y-coordinate pairs and the array is interpreted as (x1, y1, x2, y2, ..., xn, yn). If a closed polyline should be drawn, the first point must also be added at the end of the array. The DrawTriangle() and DrawQuad() methods needs all shape points as x- and y-coordinates. The DrawRectangle() function plots a rectangle out of the points that represent the minimum and maximum of the shape. The DrawEllipse() method interprets the parameters the same way, but the DrawEllipseCentered() function takes the center of the ellipse and the radii as arguments.
All methods are available for the Color structure or an integer value as color.
Usage
// Initialize the WriteableBitmap with size 512x512
WriteableBitmap writeableBmp = new WriteableBitmap(512, 512);
// Set it as source of an Image control
ImageControl.Source = writeableBmp;
// Fill the WriteableBitmap with white color
writeableBmp.Clear(Colors.White);
// Black triangle with the points P1(10, 5), P2(20, 40) and P3(30, 10)
writeableBmp.DrawTriangle(10, 5, 20, 40, 30, 10, Colors.Black);
// Red rectangle from the point P1(2, 4) that is 10px wide and 6px high
writeableBmp.DrawRectangle(2, 4, 12, 10, Colors.Red);
// Blue ellipse with the center point P1(2, 2) that is 8px wide and 5px high
writeableBmp.DrawEllipseCentered(2, 2, 8, 5, Colors.Blue);
// Closed green polyline with P1(10, 5), P2(20, 40), P3(30, 30) and P4(7, 8)
int[] p = new int[] { 10, 5, 20, 40, 30, 30, 7, 8, 10, 5 };
writeableBmp.DrawPolyline(p, Colors.Green);
// Render it!
writeableBmp.Invalidate();
Source code
You can download the Silverlight application's source code including the complete and documented ready-to-use WriteableBitmapExtensions file from here. Check out my Codeplex project WriteableBitmapEx for an up to date version of the extension methods.
To be continued...
For the next part of this series I'm planning to add fill extensions methods to the WriteableBitmap like FillRectangle(), FillEllipse(), etc.
Update 11-06-2009
Nokola optimized the DrawLine() function a bit and made it 15-30% faster than the standard DDA implementation. I've replaced the DrawLine() method in the extensions with Nokola's optimized version, fixed some bugs and updated the source code. The original DDA implementation is now called DrawLineDDA().
Thanks Nikola!
Update 11-11-2009
Nokola optimized the DrawRectangle() function and I've updated the implementation of it. I've also added a faster Clear() method without parameters that fills every pixel with a transparent color. This was also proposed by Nokola.
Thanks again Nikola!








