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