ASP.NET Image-To-HTML Conversion Reloaded

by Peter A. Bromberg, Ph.D.

Peter Bromberg
"You'll never make it as a wandering generality. You must become a meaningful specific " --Zig Ziglar

There are a number of implementations of what is commonly referred to as "IMAGE2HTML", but I believe this is the first one you may see that really succeeds in hitting the high-resolution "realistic- looking" mark.

I started with some code that is easy to find, in various forms, and in a variety of programming languages, and then modified it to work as a static class library with a ConvertImage method that accepts a URL to an image on the web, and an integer scale factor, and returns you a long string of HTML that can be added to a web page. In this case I simply assign the result HTML to a Label Control, and I"m done.



The key to the high - resolution part of it is something that a lot of programmers miss - you can only return the HTML equivalent of a pixel by its color. Since Hexadecimal color values give us a very large range that browsers are capable of rendering, that's not the issue. Once you've done that you can either display a dot "." or a number corresponding in some way to the color, but you have still missed the mark - because the default rendering behavior of the browser is to have some spacing between letters, some additional value of spacing between words, and some default further value of spacing between lines.

The net result of all this is that you get your HTML rendering of the image, but it looks crappy and washed out because of all the whitespace between the faux pixels and between each faux "line" of pixels.

So what's the answer? Its CSS! CSS style directives give us extremely fine-grained control over things like letter and word spacing, as well as line spacing. So much so, that if we aren't careful we can make a sentence look like this!

If you get the adjustments right on the CSS letter-spacing, word-spacing and line-height attributes, you can get those "faux pixels" to really pack together and look like an actual photograph. Not only that, but it does not take a lot of extra HTML because you can do this as a tag style applied to a PRE tag at the very beginning, and everything inside your <PRE> </PRE> tags will sport your cool styling! So without further bloviating, let's take a look at the class, examine the code, and look at a live sample!

Here is my "ConvertImage" class:

using System;

using System.Text;

using System.IO;

using System.Web;

using System.Net;

using System.Drawing ;

namespace PAB.Web.Utils

{   

    public class Image2Html

    {

        private Image2Html()

        {           

        }

 

        public static string ConvertImage( string imageUrl, int scale)

        {

            WebClient wc = new  WebClient();

            byte[] img = wc.DownloadData(imageUrl);

            if(img.Length >100000) return "<H1><font color=white>Sorry,Image too big for demo!</font></h1>";

            MemoryStream imgStream = new MemoryStream(img);

            Bitmap b = (Bitmap)Image.FromStream(imgStream);

            MemoryStream ms = new MemoryStream();

            StreamWriter SW = new StreamWriter(ms);

            SW.WriteLine("<!--%<---Clip Here-->");

            SW.WriteLine("<style>pre{letter-spacing:-4px;word-spacing:-4px;line-height:2px}</style>");

            SW.WriteLine("<pre><b><font size='1pt'>");   

            for(int y=0;y<b.Height;y+=scale)

            {

                for(int x=0;x<b.Width;x+=scale)

                {

                    SW.Write("<font color='#" + b.GetPixel(x,y).Name.Substring(2) + "'>");

                    SW.Write( ((byte)b.GetPixel(x,y).ToArgb())>>7 );

                    SW.Write("</font>");

                }

                SW.WriteLine();

            }

 

            SW.WriteLine("</font></b></pre>");

            SW.WriteLine("<!--%<---Clip Here-->");

            SW.Close();

            SW = null;

            byte[] b2= ms.ToArray ();

            string s = System.Text.Encoding.ASCII.GetString(b2);

            return s;

        }

    }

}

So how does it work? We receive an Image URL and an integer "scale factor" as parameters. We use the simplified WebClient class to retrieve the specified image into a byte array. In the case of this demo, if the image is over 100000 bytes I throw it away because I don't want people abusing our bandwidth here with their wise ideas.

We then wrap a new MemoryStream around our image bytes, and we use the convenient FromStream method of the Image class, casting the Image to a Bitmap. Bitmap is the "Lowest common denominator" class that allows us to manipulate pixels, get their colors, x/y coordinates and so on, regardless of the format of the original image.

Next, I create a new MemoryStream and wrap a StreamWriter around it so I can write my HTML. Finally, I scan over all the y-axis pixels and then down the x-axis, getting the pixel color with the GetPixel method. This is then used to create the <font color=#... tag, then again as a number obtained via bitshifting the 32 bit color value so I can have a single-digit number value to write out as my colored HTML. You will note that the styles I referred to earlier are applied to the PRE element in the first line:

   SW.WriteLine("<style>pre{letter-spacing:-4px;word-spacing:-4px;line-height:2px}</style>");

Finally when we are done, we get a byte array out of our MemoryStream, convert it to a big string of HTML, and return it to the caller.

The result, which you can view here as well as downloading the complete solution below, looks like this:

View the live demo here

Well! Is it HTML, or is it live? You can View Source on the page to see! Try a scale of 4 or even 3 to see real quality. This works fine in Firefart and Internet Exploder (whichever your poison), although it renders quite a bit faster in IE. I cannot think of much to do with this right now, but I bet other people can, so here is my July 4th 2006 present to you! Have a safe and happy holiday!

N.B. Thanks to Robbe for reminding me that you can get the Color object at the first GetPixel call and then re-use it without having to call GetPixel a second time. The downloadable source has been updated with this change.

Download the Solution that accompanies this article


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.
Article Discussion: