Sunday, September 05, 2004 - Posts

Render PNG from ASPX

For fun I have been building my own statistics for my blog and today I decided to display a simple hit counter. Rather than just render plain text I decided to render the hit count using a LCD type output. As for most things I do for fun I prefer to roll the code myself rather than use some ready made code. Just for interest I am providing the code that I put together to render the LCD digits, it is nothing special and could definitely be done in a much more flexible and maintainable way. What is interesting is what happened when I saved the dynamically created bitmap to the Response.OutputStream.

The first thing I did after building my image, was to save the image to a stream to determine which format would yield the smallest file, here are my results for the following image :

Image format Stream Size (Bytes)
BMP 16054
GIF 1830
JPEG 2782
PNG 570

Well, since I do have a monthly bandwidth limit imposed on me by my hosting provider (www.brinkster.com), you bet your life I decided to render the image as a PNG file. Using something similar to the following code.

Response.Clear();
Response.ContentType = "image/png"; 
img.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Png);
Response.End();

To my surprise, the Save threw the following exception:

System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+

If I tried the exact same thing using GIF or JPEG everything worked as expected. Since the PNG represented a considerable bandwidth saving I pursued the issue further and discovered that the problem occurred when ever I wrote to a non-seekable stream. So I found that by writing the PNG to a memory stream and then copying the memory stream to the Response.OutputStream everything worked just fine. Here is the a piece of code that represents something similar to what I used in the end.

using (MemoryStream stmMemory = new MemoryStream())
{
  Response.Clear();
  Response.ContentType = "image/png"; 
  img.Save(stmMemory, System.Drawing.Imaging.ImageFormat.Png );
  stmMemory.WriteTo( Response.OutputStream );
  Response.End();
}


For those of you that are interested here is the code that I used to render the LCD digits. Simplistic,
but gets the job done without a dependency on any bitmaps.
If anyone extends this to include alpha characters scaling etc. I would be glad to hear about it.
private int[,] _segments = 
{
  {5,2,20,2, 4,3,21,3, 5,4,20,4},          // Top
  {21,5,21,14, 22,4,22,15, 23,5,23,14},    // Top Right
  {23,18,23,27, 22,17,22,28, 21,18,21,27}, // Bottom Right
  {5,28,20,28, 4,29,21,29, 5,30,20,30},    // Bottom
  {2,18,2,27, 3,17,3,28, 4,18,4,27},       // Bottom Left
  {2,5,2,14, 3,4,3,15, 4,5,4,14 },         // Top Left
  {5,15,20,15, 4,16,21,16, 5,17,20,17},    // H-Middle
  {12,5,12,14, 13,4,13,15, 14,5,14,14},    // V-Middle Top
  {12,18,12,27, 13,17,13,28, 14,18,14,27}  // V-Middle Bottom
};




private
int[][] _digits = { new int[] { 0, 1, 2, 3, 4, 5 }, // 0 new int[] { 7, 8 }, // 1 new int[] { 0, 1, 3, 4, 6 }, // 2 new int[] { 0, 1, 2, 3, 6 }, // 3 new int[] { 1, 2, 5, 6 }, // 4 new int[] { 0, 2, 3, 5, 6 }, // 5 new int[] { 0, 2, 3, 4, 5, 6 }, // 6 new int[] { 0, 1, 2 }, // 7 new int[] { 0, 1, 2, 3, 4, 5, 6 }, // 8 new int[] { 0, 1, 2, 3, 5, 6 } // 9 };

private
void DrawSegment( Graphics g, Pen pen, Point pt, int segment ) { g.DrawLine( pen, pt.X + _segments[segment,0], pt.Y + _segments[segment, 1],
pt.X + _segments[segment, 2], pt.Y + _segments[segment, 3] ); g.DrawLine( pen, pt.X + _segments[segment, 4], pt.Y + _segments[segment, 5],
pt.X + _segments[segment, 6], pt.Y + _segments[segment, 7] ); g.DrawLine( pen, pt.X + _segments[segment, 8], pt.Y + _segments[segment, 9],
pt.X + _segments[segment, 10], pt.Y + _segments[segment, 11] ); }
private void DrawDigit( Graphics g, Pen on, Pen off, Point pt, byte digit ) { int i;
// Draw shaded segments
for ( i = 0; i < 9; ++i ) DrawSegment( g, off, pt, i );
// Draw Digit
for ( i = 0; i < _digits[digit].Length; ++i ) DrawSegment( g, on, pt, _digits[digit][i] ); }