Create Html Image Maps Dynamically in ASP.NET

Creating html image maps is easy using the BitMap class and its .GetPixels method in .NET.

I've been working on a financial reporting project lately and came upon the idea of bringing our charts from ChartFX.NET to life.  The idea being that we could dynamically review each pixel's color in the chart in order to generate an HTML image map on the fly.  This would enable us to apply supporting documentation in the form of links or JavaScript functions to perform some sort of action when the user moves their mouse over the chart or clicks a section of it.

.NET offers GDI+ as its tool for reviewing and manipulating images.  We can also implement unsafe code to work with images.  Since we try to avoid code marked as unsafe here, I opted to just use the Bitmap object's .GetPixel and .SetPixel methods instead.  If you have the option to implement unsafe code, you'll want to look into .LockBits and .UnLockBits.  These two methods are known to be faster for working with images.

In a nutshell, the code sample (formatted nicely for reading) below takes in an List of structs, an image tag element name, and the relative filename of the image.  The struct holds the red, green, and blue desired colors to find in the chart/image and generate the proper area tag.  My first attempt at this was to generate an area tag for each and every pixel.  Naturally, this created a huge amount of HTML for the larger images (400x400) that I was testing with.  The next logical step was to try and reduce the number of tags written by determining areas on a line by line basis in the 400 x 400 grid of pixels.

The code below iterates through the List of structs, reviews the desired color, searches line by line and pixel by pixel looking for color matches.  If it finds the desired color, it sets a marker and increments that pixel marker until it runs across a different pixel color in the image.  When this occurs, it writes the area tag with that line's coordinates and sets a new marker.  I chose the line by line method of iterating through the pixels in order to support very complex images typically generated from charting software.

For my charts and stress testing, this proved to work pretty well.  You'll find that the slowest part of this process is the .GetPixel method itself.  This is why I make one pass through to collect the colors of the image and store it in a multi-dimensional array sized to match the image height and width.  Then, reference the array by the pixel index each time we process a desired color.

ImageMaps.cs
  

using System;
using System.Data;
using System.Collections;
using System.Drawing;
using System.Web;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;

namespace EggHeadCafe.Controls
{

#region Structs
  public struct UrlsForImageColor
  {
    public string Red;
    public string Green;
    public string Blue;
    public string Url;
    public string AltTag;
    public string Target;
    public string OnMouseOver;
  }
#endregion

public class ImageMaps
{

   public ImageMaps()
   {

   }

   #region Get Map
   public string GetMap(string ImageElementName,string ImageFileName,List<EggHeadCafe.Controls.UrlsForImageColor> LinksByColor)
   {
     string sRet="";
     string Coords="";
     string CurColor="";
     string CompareColor="";
     bool fFoundColorInPixel=false;
     int hplus=0;
     int Start=0;
     int End=0;
     int y=0;
     int x=0;
     int u=0;

      Color pixelColor;

      var sb = new StringBuilder();

      EggHeadCafe.Controls.UrlsForImageColor oLink;

      Bitmap oImage = null;

      try
      {
  
        ImageFileName = System.Web.HttpContext.Current.Server.MapPath(ImageFileName);
        oImage = new Bitmap(System.Drawing.Image.FromFile(ImageFileName));
        y = oImage.Height;
        x = oImage.Width;

        string[,] ImagePixels = new string[y,x];

        for(int h = 0; h < y; h++)
        {
          for(int w = 0; w < x; w++)
          {
            pixelColor = oImage.GetPixel(h,w);     
            ImagePixels[h,w] = pixelColor.R.ToString() + "." + pixelColor.G.ToString() + "." + pixelColor.B.ToString();
           }
        }

        sb.Append("<img name=\"" + ImageElementName + "\" src=\"" + ImageFileName + "\" width=\"" + x.ToString());
        sb.Append("\" height=\"" + y.ToString() + "\" border=\"0\" usemap=\"#m_" + ImageElementName + "2\">" + "\n");
        sb.Append("<map name=\"m_" + ImageElementName + "2\">" + "\n");

        for(u = 0; u < LinksByColor.Count; u++)
        {

          oLink = (EggHeadCafe.Controls.UrlsForImageColor)LinksByColor[u];

          CompareColor = oLink.Red + "." +  oLink.Green + "." + oLink.Blue;

          for(int h = 0; h < y; h++)
          {

            for(int w = 0; w < x; w++)
            {

             if (w==0) { Start = w; }

             CurColor = ImagePixels[h,w];

             if (CurColor != CompareColor)
             {

               if (fFoundColorInPixel == true)
               {
                 Coords = h.ToString() + ",";
                 Coords += Start.ToString() + ",";
                 Coords += hplus.ToString() + ",";
                 Coords += End.ToString();
                 sb.Append(WriteArea(Coords,oLink));
               }

                Start = w;
               fFoundColorInPixel = false;
               continue;
             }
             else
             {
               fFoundColorInPixel = true;
             }

             End = w;
             hplus = h + 1;

            } // End FOR width

             if (fFoundColorInPixel == true)
             {
               Coords = h.ToString() + ",";
               Coords += Start.ToString() + ",";
               Coords += hplus.ToString() + ",";
               Coords += End.ToString();
               sb.Append(WriteArea(Coords,oLink));
             }

            }  
          }  

          sb.Append("</map>" + "\n");

          }
           catch
          {
            throw;
          }
          finally
          {
            sRet = sb.ToString();
             oImage.Dispose();
           }
  return sRet;
}
#endregion


#region Write Area
  private string WriteArea(string Coords,EggHeadCafe.Controls.UrlsForImageColor oLink)
  {
    string sRet="";
    sRet = "<area shape=\"rect\" coords=\"" + Coords + "\"";
    if (oLink.Url.Length > 0) { sRet += " href=\"" + oLink.Url + "\" "; }
    if (oLink.AltTag.Length > 0) { sRet += " alt=\"" + oLink.AltTag + "\""; }
    if (oLink.Target.Length > 0) { sRet += " target=\"" + oLink.Target + "\""; }
    if (oLink.OnMouseOver.Length > 0) { sRet += " onmouseover=\"" + oLink.OnMouseOver + "\""; }
    sRet += ">\n";
    return sRet;
  }
#endregion

  }

}


ImageMaps.aspx
  
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Drawing2D" %>
<%@ Import Namespace="System.Drawing.Imaging" %>
<%@ Import Namespace="EggHeadCafe.Controls" %>

<script Language="C#" runat="server">

protected void Page_Load(object sender, EventArgs e)
{
   var oMap = new EggHeadCafe.Controls.ImageMaps();
   var oLinks = new List<EggHeadCafe.Controls.UrlsForImageColor>();
   EggHeadCafe.Controls.UrlsForImageColor oLink;

   try
   {

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "255";
     oLink.Green = "0";
     oLink.Blue = "0";
     oLink.Url = "http://www.eggheadcafe.com";
     oLink.AltTag = "my red region";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     oLinks.Add(oLink);

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "0";
     oLink.Green = "0";
     oLink.Blue = "0";
     oLink.Url = "http://www.robbemorris.com";
     oLink.AltTag = "my black region";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     Links.Add(oLink);
                
     Response.Write(oMap.GetMap("Image1","images/pie.gif",Links));

    Links.Clear();

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "0";
     oLink.Green = "0";
     oLink.Blue = "204";
     oLink.Url = "http://www.eggheadcafe.com";
     oLink.AltTag = "blue region";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "alert('over blue region');";
     Links.Add(oLink);

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "255";
     oLink.Green = "0";
     oLink.Blue = "255";
     oLink.Url = "http://www.asp.net";
     oLink.AltTag = "pink region";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     Links.Add(oLink);

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "102";
     oLink.Green = "0";
     oLink.Blue = "153";
     oLink.Url = "http://www.yahoo.com";
     oLink.AltTag = "purple region";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     Links.Add(oLink);

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "204";
     oLink.Green = "255";
     oLink.Blue = "0";
     oLink.Url = "http://www.google.com";
     oLink.AltTag = "yellow";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     Links.Add(oLink);

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "102";
     oLink.Green = "153";
     oLink.Blue = "102";
     oLink.Url = "http://www.rushlimbaugh.com";
     oLink.AltTag = "green region";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     Links.Add(oLink);

     oLink = new EggHeadCafe.Controls.UrlsForImageColor();
     oLink.Red = "204";
     oLink.Green = "204";
     oLink.Blue = "255";
     oLink.Url = "http://www.foxnews.com";
     oLink.AltTag = "light purple";
     oLink.Target = "_blank";
     oLink.OnMouseOver = "";
     Links.Add(oLink);

     Response.Write(oMap.GetMap("Image2","images/region.gif",Links));

   }
     catch (Exception err) { Response.Write(err.Message); }

  }
          
</script>



By Robbe Morris   Popularity  (8543 Views)
Picture
Biography - Robbe Morris
Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.  Robbe also loves to scuba dive and go deep sea fishing in the Florida Keys or off the coast of Daytona Beach. Microsoft MVP