Silverlight: Super-fast Dymanic Image Generation Code (Revisited)
I decided to give a third spin of Joe Stegman's dynamic image generation code. This time, it's many times faster (about 10x) than the original implementation and the png is generated in-place (no recoding necessary).
I used some hacky optimizations :) For example, replacing the big CRC loop with a constant (0), definitely improves speed a lot! :)
There are no more intermediate memory streams or buffers: whenever SetPixel() is called, the value is written directly into PNG. Also all the header and size information is created just once. That also helped performance quite a bit.
With the new code my raindrops sample runs 20% faster (90% of the time used in calculating the drops): www.nokola.com/raindrops
Download the source code containing the new PngEncoder class here: www.nokola.com/sources/water.zip
Here's how to use the new image generator class:
PngEncoder surface = new PngEncoder(640, 480); // image dimension
surface.SetPixelSlow(40, 30, 200, 135, 32, 255); // set pixel at (40,30) with color RGBA=(200,135,32,255)
// draw a white horizontal line fast
int rowStart = surface.GetRowStart(30);
for (int i = 0; i < 10; i++) {
// SetPixelAtRowStart() is good for blitting/copying existing images onto this one
surface.SetPixelAtRowStart(i + 40, rowStart, 255, 255, 255, 255);
}
// display the image
BitmapImage img = new BitmapImage();
img.SetSource(surface.GetImageStream());
imgWater.Source = img; // this is just a normal Silverlight Image
And, here's the GetImageStream() function for comparison with the previous implementation:
public Stream GetImageStream()
{
MemoryStream ms = new MemoryStream();
ms.Write(_buffer, 0, _buffer.Length);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
Compare this to the previous function, that had numerous for()-s, encoding logic, etc :)
hoho! I'm very happy for doing this!
Edit: here's the source code for the SetPixelXX functions - to prove they are actually not slow :)
public void SetPixelSlow(int col, int row, byte red, byte green, byte blue, byte alpha)
{
int start = _rowLength * row + col * 4 + 1;
int blockNum = start / _blockSize;
start += ((blockNum + 1) * 5);
start += _dataStart;
_buffer[start] = red;
_buffer[start + 1] = green;
_buffer[start + 2] = blue;
_buffer[start + 3] = alpha;
}
public void SetPixelAtRowStart(int col, int rowStart, byte red, byte green, byte blue, byte alpha)
{
int start = rowStart + (col << 2);
_buffer[start] = red;
_buffer[start + 1] = green;
_buffer[start + 2] = blue;
_buffer[start + 3] = alpha;
}
public int GetRowStart(int row)
{
int start = _rowLength * row + 1;
int blockNum = start / _blockSize;
start += ((blockNum + 1) * 5);
start += _dataStart;
return start;
}
Comments
Anonymous
March 05, 2009
The comment has been removedAnonymous
May 26, 2009
Thanks!Anonymous
June 22, 2009
When I try to use the code above, I cannot read the generated png stream, all imaging programs say that it's not a valid png format (including .NET's libraries under WPF). Did anybody else have issues?Anonymous
June 27, 2009
Hi, Unfortunately the code strangely doesn't work with WPF, the produced image seems to be not a valid PNG image. Any ideas?Anonymous
July 01, 2009
Yes, the produced image is not a valid PNG (the CRC is set to 0). This is a trick that allows to generate the PNG much faster and it happens to work in SL 2. For WPF/other programs, check out my previous posts about PNG image generation. The generators there make valid PNGs according to the spec, but slower.Anonymous
July 01, 2009
Ah, okay, perfect. And sorry for double posting, I though the blog ate my comment. Thanks, ChristophAnonymous
July 01, 2009
no problem :) I just came back from 3 weeks holiday and saw your comment that was left for review by the blog software.Anonymous
August 17, 2009
Humm... interesting, So it is truely faster!! Thanks for bringing this upAnonymous
August 18, 2009
yw :) Silverlight 3 has a new WritableBitmap class that makes the png code above (partially) obsolete. http://www.wintellect.com/CS/blogs/jprosise/archive/2009/03/23/silverlight-3-s-new-writeablebitmap.aspx I haven't perf-ed it but I bet it's faster than the previous method.Anonymous
September 24, 2009
Hi, I have a problem with this approach when my canvas size is more than 3000... If i write the resultant image and try to open it, i am not able to view anything.. the image is corrupted... But the approach works fine for smaller images(Canvas). any idea on this?? Thanks, RaviAnonymous
September 24, 2009
I'm not sure why the image gets corrupted over 3000 size...might be an issue with the algorithm I'm using. Anyway, a better approach is to use WritableBitmap, which is now available in SL3. The approach above is for SL2, which does not have WritableBitmap. Check this blog for more info: http://www.wintellect.com/CS/blogs/jprosise/archive/2009/03/23/silverlight-3-s-new-writeablebitmap.aspx hope this helps - please let me know!