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;
}