A couple of weeks ago I got a review copy of the book 3D Game Development with Microsoft Silverlight 3: Beginner's Guide. The book has 452 pages, is written by Gastón C. Hillar and shows how to write games with Silverlight.
Although the title includes "Silverlight 3", the concepts are also valid for newer versions of Silverlight, like the current version 4.
This blog post is a short review of the book. I also have one physical copy of the book to give away. Yeah, a free book, like free beer.
The Content
The book 3D Game Development with Microsoft Silverlight 3: Beginner's Guide starts by explaining what tools are needed for Silverlight game development. Then the first simple Silverlight application is developed.
The second chapter explains what Sprites are, how they can be used for 2D games and frame based animations. Silverlight 2.5D hardware accelerations is also covered and how basic vector transformations work.
The next chapter introduces a Sprite wrapper class and how to simplify the game loop with it. Basic 2D collision detection with an axis-aligned bounding box is covered too. The author also shows in this chapter how to use keyboard input for the game control.
Chapter 4 finally brings 3D game development. It starts with a WPF XAML Browser Application (XBAP) application that draws a 3D model, which was generated with the 3D modelling tool Blender and then exported to a XAML file. After this, the 3D engine Balder is introduced and a simple application is created. To be honest, Balder has come a long way since the book was written and a lot of new features were introduced. Nevertheless, many concepts and the basics are still valid.
The next chapter explains some 3D concepts like Cameras and shows how to use them with Balder and XBAP. The 6th chapter is about input controlling and demonstrates how to use DirectInput with the XBAP application. Chapter 7 explains textures, lights and the usage of these in a game. The next chapter is all about animation.
The 9th chapter introduces the Silverlight physics engine Farseer and chapter 10 shows how to detect collisions and apply basic artificial intelligence. The next chapter adds an asteroid belt to the space game, which is used as an example throughout the book.
In Chapter 12, the author shows how to measure the game progress and how to use Expression Blend to create screens for highscore and other statistics. The next chapter continues with this and also explains the usage of pixel shaders from the WPF Pixel Shader Effects Library. Persisting settings to the IsolatedStorage is covered too. The last chapter demonstrates how to play and control audio and video with the Silverlight MediaElement.
Summary
If you want to get into game development with Silverlight, this is the right book for you. It starts with the basic tools, then shows how to write a simple 2D game, finally how to write a 3D Silverlight game with Balder and how to add a physics system. Another nice thing of this book is that the tools and techniques to create / convert content are also shown step-by-step.
3D Game Development with Microsoft Silverlight 3: Beginner's Guide lives up to its title: "Beginner's Guide". If you are a beginner in the field of Silverlight game development and don't know where to start, buy this book here.
Free Book!
I actually got two physical copies of the book and I like to give one away. If you want the book, just write a comment why you should get this gift. Make sure to include some form of contact information.
Showing posts with label Silverlight 3. Show all posts
Showing posts with label Silverlight 3. Show all posts
Monday, August 2, 2010
Monday, November 30, 2009
Convert, Encode And Decode Silverlight WriteableBitmap Data
In the comments of my Silverlight 4 EdgeCam Shots blog post "marcb" asked me how to convert the WriteableBitmap to a byte array to save the snapshot in a database.
I thought the answer might be also useful for others. Furthermore I will provide ready to use code for JPEG encoding and decoding of the WriteableBitmap.
Byte array conversion
Copy WriteableBitmap to ARGB byte array
Copy ARGB byte array into WriteableBitmap
Usage
I will include the two methods in my WriteableBitmap extensions that I'm going to put up on Codeplex soon.
JPEG encoding and decoding
If you want to store many images or transport them over a network the needed storage size could quickly become a big problem. For example an image with the size 512 x 512 needs 1 Megabyte storage space and a 1024 x 768 image even 3 MB. A solution could be image compression using JPEG encoding and decoding. To accomplish this I've used the open source FJCore JPEG library which is distributed under the MIT License and works nicely with Silverlight.
Encode WriteableBitmap as JPEG stream
Decode WriteableBitmap from JPEG stream
Usage
Keep in mind that the standard JPEG format doesn't support alpha values (transparency) and that the compression is lossy. So don't encode and decode images subsequently with JPEG.
It is also possible to use the built-in Silverlight class BitmapSource and its SetSource method to decode an JPEG stream.
Source code
Check out my Codeplex project WriteableBitmapEx for an up to date version of the byte array conversion methods.
I thought the answer might be also useful for others. Furthermore I will provide ready to use code for JPEG encoding and decoding of the WriteableBitmap.
Byte array conversion
Copy WriteableBitmap to ARGB byte array
public static byte[] ToByteArray(this WriteableBitmap bmp)
{
int[] p = bmp.Pixels;
int len = p.Length * 4;
byte[] result = new byte[len]; // ARGB
Buffer.BlockCopy(p, 0, result, 0, len);
return result;
}
Copy ARGB byte array into WriteableBitmap
public static void FromByteArray(this WriteableBitmap bmp, byte[] buffer)
{
Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
}
Usage
// Render UIElement into WriteableBitmap WriteableBitmap bmp = new WriteableBitmap(UIElement, null); // Copy WriteableBitmap.Pixels into byte array (format ARGB) byte[] buffer = bmp.ToByteArray(); // Create a new WriteableBitmap with the size of the original image WriteableBitmap bmp = new WriteableBitmap(width, height); // Fill WriteableBitmap from byte array (format ARGB) bmp.FromByteArray(buffer);
I will include the two methods in my WriteableBitmap extensions that I'm going to put up on Codeplex soon.
JPEG encoding and decoding
If you want to store many images or transport them over a network the needed storage size could quickly become a big problem. For example an image with the size 512 x 512 needs 1 Megabyte storage space and a 1024 x 768 image even 3 MB. A solution could be image compression using JPEG encoding and decoding. To accomplish this I've used the open source FJCore JPEG library which is distributed under the MIT License and works nicely with Silverlight.
Encode WriteableBitmap as JPEG stream
public static void EncodeJpeg(WriteableBitmap bmp, Stream destinationStream)
{
// Init buffer in FluxJpeg format
int w = bmp.PixelWidth;
int h = bmp.PixelHeight;
int[] p = bmp.Pixels;
byte[][,] pixelsForJpeg = new byte[3][,]; // RGB colors
pixelsForJpeg[0] = new byte[w, h];
pixelsForJpeg[1] = new byte[w, h];
pixelsForJpeg[2] = new byte[w, h];
// Copy WriteableBitmap data into buffer for FluxJpeg
int i = 0;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
int color = p[i++];
pixelsForJpeg[0][x, y] = (byte)(color >> 16); // R
pixelsForJpeg[1][x, y] = (byte)(color >> 8); // G
pixelsForJpeg[2][x, y] = (byte)(color); // B
}
}
// Encode Image as JPEG using the FluxJpeg library
// and write to destination stream
ColorModel cm = new ColorModel { colorspace = ColorSpace.RGB };
FluxJpeg.Core.Image jpegImage = new FluxJpeg.Core.Image(cm, pixelsForJpeg);
JpegEncoder encoder = new JpegEncoder(jpegImage, 95, destinationStream);
encoder.Encode();
}Decode WriteableBitmap from JPEG stream
public static WriteableBitmap DecodeJpeg(Stream sourceStream)
{
// Decode JPEG from stream
var decoder = new FluxJpeg.Core.Decoder.JpegDecoder(sourceStream);
var jpegDecoded = decoder.Decode();
var img = jpegDecoded.Image;
img.ChangeColorSpace(ColorSpace.RGB);
// Init Buffer
int w = img.Width;
int h = img.Height;
var result = new WriteableBitmap(w, h);
int[] p = result.Pixels;
byte[][,] pixelsFromJpeg = img.Raster;
// Copy FluxJpeg buffer into WriteableBitmap
int i = 0;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
p[i++] = (0xFF << 24) // A
| (pixelsFromJpeg[0][x, y] << 16) // R
| (pixelsFromJpeg[1][x, y] << 8) // G
| pixelsFromJpeg[2][x, y]; // B
}
}
return result;
}Usage
// Save rendered UIElement as JPEG file
private void BtnSaveFile_Click(object sender, RoutedEventArgs e)
{
if (saveFileDlg.ShowDialog().Value)
{
using (Stream dstStream = saveFileDlg.OpenFile())
{
// Render to WriteableBitmap
WriteableBitmap bmp = new WriteableBitmap(UIElement, null);
// Encode JPEG and write to FileStream
EncodeJpeg(bmp, dstStream);
}
}
}
// Open JPEG file and read into a WriteableBitmap
private void BtnLoadFile_Click(object sender, RoutedEventArgs e)
{
if (openFileDialog.ShowDialog().Value)
{
using (System.IO.Stream srcStream = openFileDialog.File.OpenRead())
{
// Read JPEG file and decode it
WriteableBitmap bmp = DecodeJpeg(srcStream);
}
}
}Keep in mind that the standard JPEG format doesn't support alpha values (transparency) and that the compression is lossy. So don't encode and decode images subsequently with JPEG.
It is also possible to use the built-in Silverlight class BitmapSource and its SetSource method to decode an JPEG stream.
public static WriteableBitmap DecodeJpegWithBitmapSource(Stream sourceStream)
{
// Decode JPEG from stream
var bitmapSource = new BitmapSource();
bitmapSource.SetSource(sourceStream);
return new WriteableBitmap(bitmapSource);
}Source code
Check out my Codeplex project WriteableBitmapEx for an up to date version of the byte array conversion methods.
Labels:
.Net,
C#,
JPEG,
Silverlight,
Silverlight 3,
WriteableBitmap,
WriteableBitmapEx
Thursday, November 5, 2009
Drawing Shapes - Silverlight WriteableBitmap Extensions III
Last week I've released the second part of my WriteableBitmap extensions methods. I've added DrawLine() methods and presented a sample application that showed that the WriteableBitmap line-drawing methods are 20-30 times faster than the UIElement Line class.
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
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
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!
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!
Labels:
.Net,
C#,
Computer graphics,
Silverlight,
Silverlight 3,
WriteableBitmap,
WriteableBitmapEx
Thursday, October 29, 2009
Drawing Lines - Silverlight WriteableBitmap Extensions II
The WriteableBitmap class is a nice feature that was added in Silverlight 3. It could be used to generate fast procedural images by drawing directly to a bitmap. The WriteableBitmap API is very minimalistic and there's only the raw Pixels array for such operations. This Property stores a 32 bit integer as color value for each pixel of the WriteableBitmap.
A couple of months ago I've written a handful of SetPixel methods that made it easier to use the WriteableBitmap, but they only provided a better interface for the Pixels Property and had no real functionality. This time I've written a more algorithmic method which performs a rasterization of a line: The famous DrawLine(). As the name implies is it used to draw a line between two points in the bitmap. To say it in advance: This is the way for drawing huge amounts of lines in Silverlight. I've used the elegant C# 3.0 extension methods again to add this functionality to the existing WriteableBitmap class.
Live
The application includes various scenarios where line-drawing is performed and it is also possible to set the number of lines to control the work load. The Silverlight frame rate counter at the upper left side shows the current FPS in the left-most column. You can find some details about the other parameters in this excellent blog post by András Velvárt(@vbandi).
For comparison I've also added a randomized line drawing scenario using the UIElement Line.
How it works
I've implemented two common line-drawing algorithms: The well-known Bresenham algorithm that uses only cheap integer arithmetic and a Digital Differential Analyzer (DDA) which is based upon floating point arithmetic. I don't want to explain the algorithmic details that were already explained several times in the literature or on the web. The linked Wikipedia articles are a good starting point if you are interested.
The signature of the extension methods
I have also added a Clear() method that fills the whole WriteableBitmap with a Color.
Usage
Results
The WriteableBitmap line-drawing approach is more than 20-30 times faster as UIElement Line. So if you need to draw many lines and don't need anti-aliasing or other UIELement properties, the DrawLine() extensions methods are the right choice.
One interesting fact: The Bresenham algorithm that uses only simple integer operations is not the fastest line-drawing algorithm anymore. It was one of the earliest computer graphics algorithms invented in the 1960s. Since then the hardware has changed dramatically and a simple floating point DDA technique gives almost the same results on modern hardware.
Please keep in mind that these results may differ depending on the used hardware.
Source code
You can download the sample application as a Visual Studio 2008 solution including the complete and documented ready-to-use WriteableBitmapExtensions class from here. Check out my Codeplex project WriteableBitmapEx for an up to date version of the extension methods.
Have fun and let me know it if you do some cool stuff with it.
To be continued...
I'm planning to write more shape extensions for the WriteableBitmap like DrawRectangle(), DrawEllipse(), DrawPolyline(), etc. I will publish them in follow-up blog posts.
Update 11-06-2009
Goto Drawing Shapes - Silverlight WriteableBitmap Extensions III
A couple of months ago I've written a handful of SetPixel methods that made it easier to use the WriteableBitmap, but they only provided a better interface for the Pixels Property and had no real functionality. This time I've written a more algorithmic method which performs a rasterization of a line: The famous DrawLine(). As the name implies is it used to draw a line between two points in the bitmap. To say it in advance: This is the way for drawing huge amounts of lines in Silverlight. I've used the elegant C# 3.0 extension methods again to add this functionality to the existing WriteableBitmap class.
Live
The application includes various scenarios where line-drawing is performed and it is also possible to set the number of lines to control the work load. The Silverlight frame rate counter at the upper left side shows the current FPS in the left-most column. You can find some details about the other parameters in this excellent blog post by András Velvárt(@vbandi).
For comparison I've also added a randomized line drawing scenario using the UIElement Line.
How it works
I've implemented two common line-drawing algorithms: The well-known Bresenham algorithm that uses only cheap integer arithmetic and a Digital Differential Analyzer (DDA) which is based upon floating point arithmetic. I don't want to explain the algorithmic details that were already explained several times in the literature or on the web. The linked Wikipedia articles are a good starting point if you are interested.
The signature of the extension methods
DrawLine(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color);
DrawLine(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int color);
DrawLineBresenham(this WriteableBitmap bmp,
int x1, int y1, int x2, int y2, int color);
Clear(this WriteableBitmap bmp, Color color);
The default DrawLine() method uses a DDA algorithm and is available for the Color structure and an integer value as line color. Furthermore it needs the x and y coordinate of the start point (x1, y1) and the end point (x2, y2) of the line.I have also added a Clear() method that fills the whole WriteableBitmap with a 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); // Draw a black line from P1(10, 5) to P2(20, 40) writeableBmp.DrawLine(10, 5, 20, 40, Colors.Black); // Render it! writeableBmp.Invalidate();
Results
The WriteableBitmap line-drawing approach is more than 20-30 times faster as UIElement Line. So if you need to draw many lines and don't need anti-aliasing or other UIELement properties, the DrawLine() extensions methods are the right choice.
One interesting fact: The Bresenham algorithm that uses only simple integer operations is not the fastest line-drawing algorithm anymore. It was one of the earliest computer graphics algorithms invented in the 1960s. Since then the hardware has changed dramatically and a simple floating point DDA technique gives almost the same results on modern hardware.
Please keep in mind that these results may differ depending on the used hardware.
Source code
You can download the sample application as a Visual Studio 2008 solution including the complete and documented ready-to-use WriteableBitmapExtensions class from here. Check out my Codeplex project WriteableBitmapEx for an up to date version of the extension methods.
Have fun and let me know it if you do some cool stuff with it.
To be continued...
I'm planning to write more shape extensions for the WriteableBitmap like DrawRectangle(), DrawEllipse(), DrawPolyline(), etc. I will publish them in follow-up blog posts.
Update 11-06-2009
Goto Drawing Shapes - Silverlight WriteableBitmap Extensions III
Labels:
.Net,
C#,
Computer graphics,
Silverlight,
Silverlight 3,
WriteableBitmap,
WriteableBitmapEx
Saturday, October 24, 2009
Read Between The Pixels - HLSL Kill Pixel Shader
A couple of days ago William Moore (@codenenterp) asked me via Twitter if I know a pixel shader that makes odd pixels black. I didn't know were such a shader is available, but I knew that it should be not hard to write one in HLSL. So I fired up the Shazzam tool and wrote two shaders.
I have started with a pixel shader that kills the color in every odd row of the texture and makes every second scanline transparent. If a black rectangle is overlaid with such a shaded image, each odd scanline will be seen as black, thus resulting in the desired black scanline effect.
An alternating line effect could be realized if an image instead of a black rectangle is overlaid.
After that I've extended the pixel shader to kill every odd pixel to produce the checkerboard effect William Moore wanted.
This is how a Silverlight Button could look like if the shader is applied to it:
How it works
The pixel shaders are pretty simple and small. As always, I've commented the HLSL code, but if you have any further questions, feel free to leave a comment.
Here is the shader that kills every odd scanline:
And here the shader that kills every odd pixel:
As you can see the only difference is that the kill pixel shader uses the sum of the texture coordinate's x and y component to determine if the pixel is even or odd.
For the effect of the uppermost blog post image (mandrill), I've used the product of x and y instead of the sum.
The size of the killed pixel group could be changed with the TextureSize parameter. The following example uses an eight of the original picture size as TextureSize.
Source code
I have also written a small Silverlight application that shows an image with the pixel shader attached. You can download the Visual Studio 2008 solution including the pixel shaders from here.
Photos from the USC-SIPI Image Database
I have started with a pixel shader that kills the color in every odd row of the texture and makes every second scanline transparent. If a black rectangle is overlaid with such a shaded image, each odd scanline will be seen as black, thus resulting in the desired black scanline effect.
An alternating line effect could be realized if an image instead of a black rectangle is overlaid.
After that I've extended the pixel shader to kill every odd pixel to produce the checkerboard effect William Moore wanted.
This is how a Silverlight Button could look like if the shader is applied to it:
How it works
The pixel shaders are pretty simple and small. As always, I've commented the HLSL code, but if you have any further questions, feel free to leave a comment.
Here is the shader that kills every odd scanline:
// Parameter
float2 TextureSize : register(C0);
// Sampler
sampler2D TexSampler : register(S0);
// Shader
float4 main(float2 texCoord : TEXCOORD) : COLOR
{
// Default color is fully transparent
float4 color = 0;
// Scale to int texture size
float row = texCoord.y * TextureSize.y * 0.5f;
// Calc diff between rounded half and half to get 0 or 0.5
float diff = round(row) - row;
float diffSq = diff * diff;
// Even or odd? Only even lines are sampled
if(diffSq < 0.1)
{
color = tex2D(TexSampler, texCoord);
}
return color;
}
And here the shader that kills every odd pixel:
// Parameter
float2 TextureSize : register(C0);
// Sampler
sampler2D TexSampler : register(S0);
// Shader
float4 main(float2 texCoord : TEXCOORD) : COLOR
{
// Default color is fully transparent
float4 color = 0;
// Scale to int texture size, add x and y
float2 vpos = texCoord * TextureSize * 0.5f;
float vposSum = vpos.x + vpos.y;
// Calc diff between rounded half and half to get 0 or 0.5
float diff = round(vposSum) - vposSum;
float diffSq = diff * diff;
// Even or odd? Only even pixels are sampled
if(diffSq < 0.1)
{
color = tex2D(TexSampler, texCoord);
}
return color;
}
As you can see the only difference is that the kill pixel shader uses the sum of the texture coordinate's x and y component to determine if the pixel is even or odd.
For the effect of the uppermost blog post image (mandrill), I've used the product of x and y instead of the sum.
The size of the killed pixel group could be changed with the TextureSize parameter. The following example uses an eight of the original picture size as TextureSize.
Source code
I have also written a small Silverlight application that shows an image with the pixel shader attached. You can download the Visual Studio 2008 solution including the pixel shaders from here.
Photos from the USC-SIPI Image Database
Labels:
.Net,
C#,
Computer graphics,
HLSL,
Shader,
Silverlight,
Silverlight 3
Thursday, August 20, 2009
Ye Olde Pixels - Silverlight 3 Old Movie Pixel Shader
In my latest WriteableBitmap Performance post I mentioned that the Silverlight 3 pixel shaders run really fast, although they are executed on the CPU and not on the GPU and that nice real-time effects can be done with it. A Silverlight 3 pixel shader could also be applied to any UIElement, thus the MediaElement too. The Silverlight MediaElement is mainly used to play music or video and with an attached shader many cool effects could be realized. As you might know I like nice effects and due to this passion I have coded another pixel shader for Silverlight. This time I have implemented a shader that simulates scratches, noise and other effects you might have seen in old movies.Live
The intensity of the scratches and the noise is controlled with the Sliders. The shader could be disabled with the "Bypass" checkbox and it's also possible to load a WMV clip with the "Load" Button.
William Moore made some short video clips with After Effects for me and I have uploaded them to my Dropbox. Just copy a URL to the TextBox and press "Load" to try them with the shader:
- Grass.wmv
- MooreFamily.wmv
How it works
Here's the pixel shader written in HLSL:
// Parameters
float ScratchAmount : register(C0);
float NoiseAmount : register(C1);
float2 RandomCoord1 : register(C2);
float2 RandomCoord2 : register(C3);
float Frame : register(C4);
// Static
static float ScratchAmountInv = 1.0 / ScratchAmount;
// Sampler
sampler2D TexSampler : register(S0);
sampler2D NoiseSampler : register(S1);
// Shader
float4 main(float2 uv : TEXCOORD) : COLOR
{
// Sample texture
float4 color = tex2D(TexSampler, uv);
// Add Scratch
float2 sc = Frame * float2(0.001f, 0.4f);
sc.x = frac(uv.x + sc.x);
float scratch = tex2D(NoiseSampler, sc).r;
scratch = 2 * scratch * ScratchAmountInv;
scratch = 1 - abs(1 - scratch);
scratch = max(0, scratch);
color.rgb += scratch.rrr;
// Calculate random coord + sample
float2 rCoord = (uv + RandomCoord1 + RandomCoord2) * 0.33;
float3 rand = tex2D(NoiseSampler, rCoord);
// Add noise
if(NoiseAmount > rand.r)
{
color.rgb = 0.1 + rand.b * 0.4;
}
// Convert to gray + desaturated Sepia
float gray = dot(color, float4(0.3, 0.59, 0.11, 0));
color = float4(gray * float3(0.9, 0.8, 0.6) , 1);
// Calc distance to center
float2 dist = 0.5 - uv;
// Random light fluctuation
float fluc = RandomCoord2.x * 0.04 - 0.02;
// Vignette effect
color.rgb *= (0.4 + fluc - dot(dist, dist)) * 2.8;
return color;
}The 1st effect adds some random scratches, which are moving slowly each frame.
Unfortunately the Shader Model 2 doesn't support any random function and the noise() intrinsic could not be used for real-time effects. That's a problem if you want to make pseudorandom-based shaders, but one key element of restricted shader development is pre calculation and texture lookup. The randomness, which is used in the shader, comes from a WriteableBitmap, that is filled with random color in the initialization step of the application. The shader in turn samples random values from that texture.
The 2nd effect adds a bit random noise to the image. If the texture coordinates with the frame counter would only be used, a static, moving pattern would be seen and it wouldn't look noisy at all. So actually a new random noise texture is needed every frame to achieve the desired effect. Generating a random texture with a WriteableBitmap in every frame would be too expensive. Instead of this, a little trick is used and only four random numbers are generated every frame and passed to the shader as parameters. These random seeds are then used as texture coordinates for a random lookup in the random colored texture. Therefore each pixel in every frame samples a different random value. Although it is not highly random, the values are sufficient enough to produce changing noise.
The 3rd step converts the pixel to grayscale and colors it afterwards with a desaturated Sepia tone.
The 4th and last effect computes the distance to the center and multiplies it with the color. This generates the vignette effect as a fadeout to black towards the edge. A random light flickering of an old projector is also simulated.
That's it and it's all about cheating.
Source code
Feel free to download the Visual Studio 2008 solution including the pixel shader from here.
Update 12-29-2009
I refactored the code behind shader class and separated it completly from the calling code (MainPage.xaml.cs). Now the shader class generates a default noise texture and random coordinates itself.
The linked source code was updated.
Update 12-31-2009
See this blog post if you want to try the shader in real time with your own webcam.
Labels:
.Net,
C#,
Computer graphics,
HLSL,
Shader,
Silverlight,
Silverlight 3
Wednesday, August 5, 2009
Silverlight 3 WriteableBitmap Performance Follow-Up
Two weeks ago I wrote the Silverlight 3 WriteableBitmap Performance blog post and got some good response on it. One of it came from the author of Quakelight, Julien Frelat. He contacted and asked me if I had tested his PNG wrapper technique, which is used in the Silverlight port of the famous game Quake. I thought Quakelight uses the WriteableBitmap and not a custom Stream hack, so I haven’t considered to check it for the Speedtest. Actually Quakelight uses the Silverlight 3 WriteableBitmap, but the source code includes a custom Stream for Silverlight 2 too.
For better performance the Quakelight PngWrapper and BitmapData classes use a 8 bit color palette instead of full 32 bit ARGB colors. This fact makes it not directly comparable to the other competitors, which all support 32 bit ARGB colors, but for certain problems 256 colors could be sufficient. That’s why I wrote this follow-up and integrated Quakelight’s Silverlight 2 Stream implementation.
The Speedtest generates an interference image and writes each pixel to a buffer, which is then used as the source of an Image. This effect could also be realized with a pixel shader and as I was working on the Speedtest v2, I implemented the effect as a pixel shader too.
Make sure to check the Update section at the bottom of this post.
The Competitors
Live
The application measures the time, which every implementation needs to draw the "Maximum Frames" and calculates the mean frames per seconds (fps). The third text column shows the relative performance compared to the WriteableBitmap.
If the tests complete very fast, you should increase the "Maximum Frames" to get better results.
How it works
The image is still 512 x 512 pixels in size and the mean frames per second are measured, but I changed the CalculateColor(int x, int y) effect a bit and removed the lookup table for the sine movement of the circles. The effect looks almost the same, but the code is much better to understand. At least I hope so.
The calculation uses normalized texture coordinates and the circle centers are computed each frame and stored in the Points C1 and C2:
The normalization of the center coordinates makes it easier to use the same values directly as parameters for the pixel shader:
Results
The results differ a bit from the first article, which is caused by the other algorithm that is used here. There are other color distributions and thus results in a slightly different drawing. The Silverlight rendering has a great impact on the performance and unfortunately Silverlight doesn't ship with the .Net Stopwatch class and the only way to get suitable data, is the measuring of larger code blocks, including the drawing, with the imprecise DateTime struct.
The Quakelight BitmapData is almost as fast as the Silverlight 3 WriteableBitmap, but at the cost of the 256 color limitation. Although the pixel shader is not executed on the GPU, it still runs ultra-fast compared to the other competitors.
If you want to implement a procedural image generation technique I recommend to try a pixel shader, but keep in mind that Silverlight 3 only supports the limited Shader Model 2. I noticed that the Silverlight pixel shaders seem to use SEE and are automatically executed in parallel if they run on a multi-core processor. The framerate on a dual-core machine was twice as high as on a single-core CPU. This parallel software shader implementaion in Silverlight is actually the only right way to implement them and nothing special. Shaders are designed for parallel execution on the GPU.
Source code
Download the C# code and the pixel shader from here.
Update 08-10-2009
Justin Harrell used my Speedtest source code and added tests for the Silverlight 3 MediaSource API. He contacted my via Email and attached the source code:
Thanks Justin for sharing your additions. That's why I <3 open source.
For better performance the Quakelight PngWrapper and BitmapData classes use a 8 bit color palette instead of full 32 bit ARGB colors. This fact makes it not directly comparable to the other competitors, which all support 32 bit ARGB colors, but for certain problems 256 colors could be sufficient. That’s why I wrote this follow-up and integrated Quakelight’s Silverlight 2 Stream implementation.
The Speedtest generates an interference image and writes each pixel to a buffer, which is then used as the source of an Image. This effect could also be realized with a pixel shader and as I was working on the Speedtest v2, I implemented the effect as a pixel shader too.
Make sure to check the Update section at the bottom of this post.
The Competitors
- Silverlight 3 WriteableBitmap.
- RawPngBufferStream from the open source GameEngine Balder.
- Nikola's PngEncoder, which is an improved version of Joe Stegman's work.
- Ian Griffiths' SlDynamicBitmap library.
- NEW: Quakelight’s 8 bit BitmapData.
- NEW: A pixel shader.
Live
The application measures the time, which every implementation needs to draw the "Maximum Frames" and calculates the mean frames per seconds (fps). The third text column shows the relative performance compared to the WriteableBitmap.
If the tests complete very fast, you should increase the "Maximum Frames" to get better results.
How it works
The image is still 512 x 512 pixels in size and the mean frames per second are measured, but I changed the CalculateColor(int x, int y) effect a bit and removed the lookup table for the sine movement of the circles. The effect looks almost the same, but the code is much better to understand. At least I hope so.
private void CalculateColor(int x, int y)
{
// Normalize coordinates
double xn = x * TexSizeInv;
double yn = y * TexSizeInv;
// Overlayed sine circle rings
// I use member variables for argb,
// so I don't have to allocate and return a byte array in each call
// Ugly, but much faster than return new byte[]{ }
// Red
double d = (xn - C1.X) * (xn - C1.X) + (yn - C1.Y) * (yn - C1.Y);
r = (byte)((Math.Sin(d * Frequency)) > 0 ? 0 : 255);
// Blue
d = (xn - C2.X) * (xn - C2.X) + (yn - C2.Y) * (yn - C2.Y);
b = (byte)((Math.Sin(d * Frequency)) > 0 ? 0 : 255);
// Green fills the gaps
g = (byte)~(r | b);
}The calculation uses normalized texture coordinates and the circle centers are computed each frame and stored in the Points C1 and C2:
private void CalculateCenters()
{
// Nice sine circle movement
// Use normalized coordinates
C1.X = Math.Sin(framesCount * 0.02) * Half + Quarter;
C1.Y = Math.Sin(framesCount * 0.08) * Half + Quarter;
C2.X = Math.Sin(framesCount * 0.10) * Half + Quarter;
C2.Y = Math.Sin(framesCount * 0.04) * Half + Quarter;
}The normalization of the center coordinates makes it easier to use the same values directly as parameters for the pixel shader:
// Parameters
float2 C1 : register(C0);
float2 C2 : register(C1);
float Frequency : register(C2);
// Shader
float4 main(float2 p : TEXCOORD) : COLOR
{
// Overlayed sine circle rings
float4 color = 1;
// Red
float2 dist = uv - C1;
color.r = sin(dot(dist, dist) * Frequency) > 0 ? 0 : 1;
// Blue
dist = uv - C2;
color.b = sin(dot(dist, dist) * Frequency) > 0 ? 0 : 1;
// Green fills the gaps
color.g = color.r + color.b > 0 ? 0 : 1;
return color;
}Results
The results differ a bit from the first article, which is caused by the other algorithm that is used here. There are other color distributions and thus results in a slightly different drawing. The Silverlight rendering has a great impact on the performance and unfortunately Silverlight doesn't ship with the .Net Stopwatch class and the only way to get suitable data, is the measuring of larger code blocks, including the drawing, with the imprecise DateTime struct.
The Quakelight BitmapData is almost as fast as the Silverlight 3 WriteableBitmap, but at the cost of the 256 color limitation. Although the pixel shader is not executed on the GPU, it still runs ultra-fast compared to the other competitors.
If you want to implement a procedural image generation technique I recommend to try a pixel shader, but keep in mind that Silverlight 3 only supports the limited Shader Model 2. I noticed that the Silverlight pixel shaders seem to use SEE and are automatically executed in parallel if they run on a multi-core processor. The framerate on a dual-core machine was twice as high as on a single-core CPU. This parallel software shader implementaion in Silverlight is actually the only right way to implement them and nothing special. Shaders are designed for parallel execution on the GPU.
Source code
Download the C# code and the pixel shader from here.
Update 08-10-2009
Justin Harrell used my Speedtest source code and added tests for the Silverlight 3 MediaSource API. He contacted my via Email and attached the source code:
Hello
I have been looking into Silverlight Graphics performance myself and was interested in the new SL3 features. So I read your blog entry for WriteableBitmap performance and was also interested in the new MediaSource managed codec abilities, which looked like another way to get pixels to the screen, but also audio as well which could be interesting for games etc.
So I took your source for the test sample and added two MediaSource tests, one using single threaded frame generation, the second using a background thread and a double buffer based on work from Pete Brown at on his Commodore 64 emulator in Silverlight.
I also reworked the UI with checkboxes to turn on/off tests as well as adding 3 new types of tests beyond the Circle interference to test performance of the rendering method vs the pixel generation. These include a random noise, simple scrolling line, and a no op. I also refactored some to make it easier to add new test types. I did not implement shader based versions of my tests, so if you run the shader test on anything but circle interference its basically a no op for now J.
I thought it might be an interesting addition to the sample as yet another way to generate dynamic bitmaps, the mediasource performs very well, although still behind WriteableBitmap.
Note for the non-double buffered version of MediaSource there is a padTime that can be tuned, it is currently set to 10ms and can be lowered to improve frame rate, but below a certain time it will get too short based on how faster your computer is and cause the media player to think it is losing frames and start skipping badly. I haven’t figured out a good way to compensate for this automatically yet, it has to do with how long the video render takes in addition to the pixel generation. The double buffer does not have this issue as it is a fixed frame rate set by frameTime.
I have attached the sample to this email, let me know what you think, and thanks for the great blog posts.
Justin Harrell
Thanks Justin for sharing your additions. That's why I <3 open source.
Labels:
.Net,
C#,
Computer graphics,
Demoscene,
HLSL,
Shader,
Silverlight,
Silverlight 3,
WriteableBitmap
Thursday, July 30, 2009
Sharp Edge - Silverlight Parametric Pixel Shader
One great addition to Silverlight 3 are pixel shaders. The Blur and the Drop Shadow shaders are bundled with Silverlight 3, but it's also possible to attach custom shaders to any UI Element. Unfortunately shaders are not executed on the GPU by Silverlight, but the Software implementation is pretty fast.
There are many cool effects, which are not already part of the WPF Pixel Shader Effects Library, one could implement. So only the sky is the limit - and the Shader Model 2.0 instruction count limit of course.
Silverlight pixel shaders could be written in HLSL and compiled with the DirectX shader compiler fxc. The produced binary file is then loaded with the Silverlight 3 PixelShader class. Quite easy huh? I must admit that I'm not a complete newbie to shader development. I even wrote some shaders with Shader Model 1.1 in the assembler shading language a few years ago, but haven't done it a while.
For Silverlight 3 I've implemented an edge detection post processing effect. It's a parametric pixel shader, which performs a common image processing technique called convolution.
Live
The initial image "Lenna" is a famous test picture for image processing algorithms. Daniel Collin (@daniel_collin) pointed me on the interesting story behind that picture of Lena Söderberg. Although Lena is a pretty lady, you should try another image too.
You can control the threshold of the edge detection with the Slider and disable the shader with the "Bypass" CheckBox. Select one of three preset convolution operators from the ComboBox: Scharr, Prewitt and the well-known Sobel operator. It's also possible to change the first column of the Gx convolution kernel to try your own operator. Actually two 3x3 convolution kernels are used by the algorithm. One for the horizontal (Gx) and another one for the vertical (Gy) direction. The Gy is just a 90° rotation of Gx and the last column of Gx is the inverse of the first column. The middle column is zero. So instead of 18 parameters (3x3x2) only 3 parameters need to be passed to the shader.
How it works
I used the Shazzam Tool for the shader development. It's a nice tool to write and test shaders for WPF. It also generates a corresponding class for the pixel shader, which is then used in XAML. Most of the controls take advantage of Silverlight's 3 great (Element) Data binding mechanism.
There's really nothing more to write about the Silverlight application, all the magic happens in the pixel shader:
Source code
The Visual Studio 2008 solution including the pixel shader is available from here.
Update 12-30-2009
See this blog post if you want to try the shader in real time with your own webcam.
Razor photo by Jake Sutton
There are many cool effects, which are not already part of the WPF Pixel Shader Effects Library, one could implement. So only the sky is the limit - and the Shader Model 2.0 instruction count limit of course.
Silverlight pixel shaders could be written in HLSL and compiled with the DirectX shader compiler fxc. The produced binary file is then loaded with the Silverlight 3 PixelShader class. Quite easy huh? I must admit that I'm not a complete newbie to shader development. I even wrote some shaders with Shader Model 1.1 in the assembler shading language a few years ago, but haven't done it a while.
For Silverlight 3 I've implemented an edge detection post processing effect. It's a parametric pixel shader, which performs a common image processing technique called convolution.
Live
The initial image "Lenna" is a famous test picture for image processing algorithms. Daniel Collin (@daniel_collin) pointed me on the interesting story behind that picture of Lena Söderberg. Although Lena is a pretty lady, you should try another image too.
You can control the threshold of the edge detection with the Slider and disable the shader with the "Bypass" CheckBox. Select one of three preset convolution operators from the ComboBox: Scharr, Prewitt and the well-known Sobel operator. It's also possible to change the first column of the Gx convolution kernel to try your own operator. Actually two 3x3 convolution kernels are used by the algorithm. One for the horizontal (Gx) and another one for the vertical (Gy) direction. The Gy is just a 90° rotation of Gx and the last column of Gx is the inverse of the first column. The middle column is zero. So instead of 18 parameters (3x3x2) only 3 parameters need to be passed to the shader.
How it works
I used the Shazzam Tool for the shader development. It's a nice tool to write and test shaders for WPF. It also generates a corresponding class for the pixel shader, which is then used in XAML. Most of the controls take advantage of Silverlight's 3 great (Element) Data binding mechanism.
There's really nothing more to write about the Silverlight application, all the magic happens in the pixel shader:
// Parameters
float Threshhold : register(C0);
float K00 : register(C1); // Kernel first column top
float K01 : register(C2); // Kernel first column middle
float K02 : register(C3); // Kernel first column bottom
float2 TextureSize : register(C4);
// Static Vars
static float ThreshholdSq = Threshhold * Threshhold;
static float2 TextureSizeInv = 1.0 / TextureSize;
static float K20 = -K00; // Kernel last column top
static float K21 = -K01; // Kernel last column middle
static float K22 = -K02; // Kernel last column bottom
sampler2D TexSampler : register(S0);
// Shader
float4 main(float2 uv : TEXCOORD) : COLOR
{
// Calculate pixel offsets
float2 offX = float2(TextureSizeInv.x, 0);
float2 offY = float2(0, TextureSizeInv.y);
// Sample texture
// Top row
float2 texCoord = uv - offY;
float4 c00 = tex2D(TexSampler, texCoord - offX);
float4 c01 = tex2D(TexSampler, texCoord);
float4 c02 = tex2D(TexSampler, texCoord + offX);
// Middle row
texCoord = uv;
float4 c10 = tex2D(TexSampler, texCoord - offX);
float4 c12 = tex2D(TexSampler, texCoord + offX);
// Bottom row
texCoord = uv + offY;
float4 c20 = tex2D(TexSampler, texCoord - offX);
float4 c21 = tex2D(TexSampler, texCoord);
float4 c22 = tex2D(TexSampler, texCoord + offX);
// Convolution
float4 sx = 0;
float4 sy = 0;
// Convolute X
sx += c00 * K00;
sx += c01 * K01;
sx += c02 * K02;
sx += c20 * K20;
sx += c21 * K21;
sx += c22 * K22;
// Convolute Y
sy += c00 * K00;
sy += c02 * K20;
sy += c10 * K01;
sy += c12 * K21;
sy += c20 * K02;
sy += c22 * K22;
// Add and apply Threshold
float4 s = sx * sx + sy * sy;
float4 edge = 1;
edge = 1 - float4( s.r <= ThreshholdSq,
s.g <= ThreshholdSq,
s.b <= ThreshholdSq,
0); // Alpha is always 1!
return edge;
}
The colors of the current pixel's neighbors are sampled from the image, which are then multiplied with the corresponding kernel value. The results are summed up, the threshold is applied and the new color is returned. I optimized the operation a bit more by calculating some static variables, using the squared threshold and leaving the calculation for the zero kernel column / row out. Source code
The Visual Studio 2008 solution including the pixel shader is available from here.
Update 12-30-2009
See this blog post if you want to try the shader in real time with your own webcam.
Razor photo by Jake Sutton
Labels:
.Net,
C#,
Computer graphics,
HLSL,
Shader,
Silverlight,
Silverlight 3
Friday, July 24, 2009
Silverlight 3 WriteableBitmap Performance
Having a fast dynamic bitmap generation API at hand is essential for procedural image generation and a lot of computer games. Therefore many Silverlight developers were disappointed that WPFs WriteableBitmap wasn't available before Silverlight 3. Fortunately there was the BitmapImage.SetSource method, which uses a Stream as parameter for the bitmap source and could be used to fill an Image. I think Joe Stegman was the first who wrote a custom PNG Genertor Stream, which used this Stream mechanism and made it possible to generate procedural images with Silverlight 2. Other implementations followed.
Now that we have Silverlight 3 and the WriteableBitmap class, all these custom PNG Stream implementations became obsolete. There are still some developers, who complain about the performance of the WriteableBitmap. I was curious how the custom PNG Stream implementations compete with the WriteableBitmap and how big the speed difference really is. That's why I wrote a small Silverlight 3 application, which measures the frames per second of the custom PNG Stream implementations and the Silverlight 3 WriteableBitmap.
The Competitors
Live
The application measures the time, which every implementation needs to draw the "Maximum Frames" and calculates the mean frames per seconds (fps). The third text column shows the relative performance compared to the WriteableBitmap.
If the tests complete very fast, you should increase the "Maximum Frames" to get right results.
How it works
The Image has the size 512 x 512. One after another every drawing method is executed and the time is measured. The method CalculateColor(int x, int y) computes the color for every pixel. For that I implemented a nice old school demoscene effect, which produces an interference image. I was inspired by the brilliant Amiga demo State of the Art from 1992.
The circles center position is animated with a sine function. For better performance I use a pre calculated lookup table (LUT) here. The rings are built using the clamped Euclidian distance to the center. Actually every Math.Sqrt() produces one colored circle, which is cut into rings by the shifting and clamping. Of course this could also be done with some loops and sine / cosine or other techniques. The square roots are calculated on the fly and not stored in a LUT. Otherwise the calculation would be too fast and not representing a real use case.
The rest of the implementation is quite simple and there's not much to explain. If you are interested in the details, please look at the source code or write a comment.
Results
The WriteableBitmap is obviously the fastest implementation. Actually I haven't expected anything else, but I hoped it would be a bit faster. Nevertheless, the Silverlight 3 WriteableBitmap is almost twice as fast as the SlDynamicBitmap library and Balder's RawPngBufferStream.
Please consider, although I use relative values, you might encounter some slightly different test results. Depending on the used hardware each implementation could perform better or worse.
Source code
The Visual Studio 2008 solution of the Speedtest application is available for download from here.
Update 08-06-2009
I've written a follow-up to this article and included the Quakelight PNG implementation and a custom pixel shader.
Now that we have Silverlight 3 and the WriteableBitmap class, all these custom PNG Stream implementations became obsolete. There are still some developers, who complain about the performance of the WriteableBitmap. I was curious how the custom PNG Stream implementations compete with the WriteableBitmap and how big the speed difference really is. That's why I wrote a small Silverlight 3 application, which measures the frames per second of the custom PNG Stream implementations and the Silverlight 3 WriteableBitmap.
The Competitors
- Silverlight 3 WriteableBitmap.
- RawPngBufferStream from the open source GameEngine Balder, which I used for my Perlin Noise sample.
- Nikola's PngEncoder, which is an improved version of Joe Stegman's work.
- Ian Griffiths' SlDynamicBitmap library.
Live
The application measures the time, which every implementation needs to draw the "Maximum Frames" and calculates the mean frames per seconds (fps). The third text column shows the relative performance compared to the WriteableBitmap.
If the tests complete very fast, you should increase the "Maximum Frames" to get right results.
How it works
The Image has the size 512 x 512. One after another every drawing method is executed and the time is measured. The method CalculateColor(int x, int y) computes the color for every pixel. For that I implemented a nice old school demoscene effect, which produces an interference image. I was inspired by the brilliant Amiga demo State of the Art from 1992.
private void CalculateColor(int x, int y)
{
// Nice sine circle movement.
int x1 = (int)(sin[FramesCount * 1] * TexSizeHalf) + TexSizeQuarter;
int y1 = (int)(sin[FramesCount * 4] * TexSizeHalf) + TexSizeQuarter;
int x2 = (int)(sin[FramesCount * 5] * TexSizeHalf) + TexSizeQuarter;
int y2 = (int)(sin[FramesCount * 2] * TexSizeHalf) + TexSizeQuarter;
// Clamped Euclidean distance as color
// Change the multiplication Factor to get more circles
// Change the clamping Threshold for the space between
int d = (x - x1) * (x - x1) + (y - y1) * (y - y1);
r = (byte)((byte)Math.Sqrt(d << Factor) > Threshold ? 255 : 0);
d = (x - x2) * (x - x2) + (y - y2) * (y - y2);
b = (byte)((byte)Math.Sqrt(d << Factor) > Threshold ? 255 : 0);
// Fill the gaps with green
g = (byte)~(r | b);
}
The circles center position is animated with a sine function. For better performance I use a pre calculated lookup table (LUT) here. The rings are built using the clamped Euclidian distance to the center. Actually every Math.Sqrt() produces one colored circle, which is cut into rings by the shifting and clamping. Of course this could also be done with some loops and sine / cosine or other techniques. The square roots are calculated on the fly and not stored in a LUT. Otherwise the calculation would be too fast and not representing a real use case.
The rest of the implementation is quite simple and there's not much to explain. If you are interested in the details, please look at the source code or write a comment.
Results
The WriteableBitmap is obviously the fastest implementation. Actually I haven't expected anything else, but I hoped it would be a bit faster. Nevertheless, the Silverlight 3 WriteableBitmap is almost twice as fast as the SlDynamicBitmap library and Balder's RawPngBufferStream.
Please consider, although I use relative values, you might encounter some slightly different test results. Depending on the used hardware each implementation could perform better or worse.
Source code
The Visual Studio 2008 solution of the Speedtest application is available for download from here.
Update 08-06-2009
I've written a follow-up to this article and included the Quakelight PNG implementation and a custom pixel shader.
Labels:
.Net,
C#,
Computer graphics,
Demoscene,
Silverlight,
Silverlight 3,
WriteableBitmap
Thursday, July 16, 2009
WriteableBitmap Extension Methods
Bill Reiss (@billreiss) wrote a good blog post about the pixel format of the Silverlight 3 WriteableBitmap class and included some nice helper methods for the pixel manipulation. I used his two methods, optimized them a bit and packed them into a static class as extension methods of the WriteableBitmap. I also added some methods, which take the System.Windows.Media.Color structure as input parameter instead of bytes.
The SetPixeli overloads of the methods use the precalculated index and don't calculate the index position itself in every call and are therefore faster.
The signature of the methods
Usage
This short code snippet produces the image you can see above. It's really simple algorithmic beauty from the alpha channel.
Source code
You can download the complete Visual Studio 2008 solution from here. Check out my Codeplex project WriteableBitmapEx for an up to date version of the extension methods.
The SetPixeli overloads of the methods use the precalculated index and don't calculate the index position itself in every call and are therefore faster.
The signature of the methods
SetPixeli(this WriteableBitmap bmp, int index, byte r, byte g, byte b); SetPixel(this WriteableBitmap bmp, int x, int y, byte r, byte g, byte b); SetPixeli(this WriteableBitmap bmp, int index, byte a, byte r, byte g, byte b); SetPixel(this WriteableBitmap bmp, int x, int y, byte a, byte r, byte g, byte b); SetPixeli(this WriteableBitmap bmp, int index, Color color); SetPixel(this WriteableBitmap bmp, int x, int y, Color color); SetPixeli(this WriteableBitmap bmp, int index, byte a, Color color); SetPixel(this WriteableBitmap bmp, int x, int y, byte a, Color color);
Usage
int index = 0;
for (int y = 0; y < writeableBmp.PixelHeight; y++)
{
for (int x = 0; x < writeableBmp.PixelWidth; x++)
{
byte alpha = (byte)(x * x + y * y);
writeableBmp.SetPixeli(index++, alpha, Colors.Black);
}
}
writeableBmp.Invalidate();
The index position isn't calculated with x * writeableBmp.PixelWidth + y in every iteration, instead I use an extra index variable. The incrementation is a lightweight computational operation compared to the multiplication + addition.This short code snippet produces the image you can see above. It's really simple algorithmic beauty from the alpha channel.
Source code
You can download the complete Visual Studio 2008 solution from here. Check out my Codeplex project WriteableBitmapEx for an up to date version of the extension methods.
Labels:
.Net,
C#,
Computer graphics,
Silverlight,
Silverlight 3,
WriteableBitmap,
WriteableBitmapEx
Subscribe to:
Posts (Atom)



















