Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Using a Control Adapter to Properly Display PNG Images in IE

4.75/5 (22 votes)
25 Oct 20067 min read 1   315  
Internet Explorer 5.5 and 6 cannot display transparent PNG images using the standard IMG tag. Here's a solution that uses a control adapter to output different HTML when the image to display is a PNG.

Sample Image - IePngControlAdapter.jpg

Introduction

If you've ever dealt with transparent PNG images on your web site, then you are well aware of the display issue with IE 5.5 and IE 6. There are several different solutions that can be found on the web. Here's one that uses a control adapter to output different HTML for PNG images.

Background

The image above shows the problem IE has when displaying a transparent PNG using the standard IMG tag:

HTML
<img src="sample.png" width="180" height="130">

It just doesn't know what to do with the transparency information. Microsoft's solution explains how to use their AlphaImageLoader filter as an alternate way to display the image. This filter is available to IE as of version 5.5. What they suggest is rendering different HTML for IE 5.5/6 clients. What an annoyance.

Existing Solutions

If you search the web for solutions to this problem, you'll find quite a few. One of the best is from Bob Osola. It uses JavaScript to modify the document on the client side. Beautiful! However, it does require JavaScript to be enabled on the client.

Let's look at an alternative solution using the a new feature called control adapters introduced with ASP.NET 2.0. Even if Bob's JavaScript method works for you, this solution may inspire you to use control adapters in other unique ways.

Control Adapter Solution

Control Adapters are a means for you to intercept the rendering functions of a control and replace them with your own. There's a great article by Fritz Onion in the October 2006 edition of MSDN magazine explaining control adapters in more detail than we will here.

We will write a control adapter to adapt the output from the ASP.NET Image Web Control. Let's begin by looking at the code:

C#
public class ImageControlAdapter : WebControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Image  oImage  = Control as Image;
    string strPath = Page.MapPath(oImage.ImageUrl);

    // If not a PNG...
    if (!strPath.EndsWith(".png", 
        StringComparison.OrdinalIgnoreCase))
    {
      base.Render(writer); // Have control render itself
      return;
    }

    writer.AddAttribute(HtmlTextWriterAttribute.Id, img.ClientID);
    writer.AddStyleAttribute(HtmlTextWriterStyle.Filter,
    "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
    Page.ResolveClientUrl(img.ImageUrl) + "')");

    if (!img.Width.IsEmpty)
      writer.AddStyleAttribute(HtmlTextWriterStyle.Width, 
                               img.Width.ToString());

    if (!img.Height.IsEmpty)
      writer.AddStyleAttribute(HtmlTextWriterStyle.Height, 
                               img.Height.ToString());

    writer.RenderBeginTag(HtmlTextWriterTag.Span);
    writer.RenderEndTag();
  }
}

We begin our public class by deriving it from WebControlAdapter which contains some basic rendering implementations that simply allow the original Image control to do the rendering. The WebControlAdapter class contains an important member named Control that is a reference to the original Image control. Casting the Control member to an Image object, we can access its properties to output our own HTML as needed.

Given an image tag that looks like this:

HTML
<asp:image id="Image1" width="180" height="130" 
           imageurl="Sample.png" runat="server" />

We want to output this HTML:

HTML
<div id="Image1"
 style="filter:progid:DXImageTransform.
        Microsoft.AlphaImageLoader(src='Sample.png');
        width:180px;height:130px;">
</div>

Notice, though, that we only change the output when the image URL references a file with a PNG extension. Otherwise, we let the original Image control render its own output.

Hooking It Up

There's one more detail to attend to. We need to configure the web site so our control adapter gets associated with the standard Image control. We also want this control adapter used only when the client is using IE5.5 or IE6. (IE7 is suppose to have native transparent PNG support. Versions previous to IE5.5 don't have the AlphaImageLoader filter, so there's no way to get PNGs to display properly.) To hook it all up, we add a browser file into our App_Browsers folder with this content:

XML
<browsers>
  <browser refID="IE55">
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Image" 
               adapterType="ImageControlAdapter" />
    </controlAdapters>
  </browser>
  <browser id="IE6" parentID="IE6to9">
    <identification>
      <capability name="majorversion" match="6" />
    </identification>
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Image" 
               adapterType="ImageControlAdapter" />
    </controlAdapters>
  </browser>
</browsers>

There are plenty of web resources explaining the format of a browser file. For us, the important section is controlAdapters where we associate our adapter with the standard Image control. The first browser element simply adds the control adapter to a IE55 definition created by the standard browser definition files. The second browser element filters an existing definition (IE6to9) to create a new definition specific to IE6.

Even Better Ideas

One of the most humbling things about CodeProject is how quickly you learn how little you know. It never fails. After writing an article, someone (usually very kindly) will point out a better way to solve the problem.

Thus is the case here with this article. Shortly after posting, I received a message from Michael and another from Richard pointing out how much easier it would be to use the IMG tag rather than using the DIV tag for the output. All we have to do is add the AlphaImageLoader filter, and replace the ImageURL attribute with a one pixel transparent GIF. We then let the Image control renter itself as normal.

C#
public class ImageControlAdapter : WebControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Image  oImage  = Control as Image;
    string strPath = Page.MapPath(oImage.ImageUrl);

    // If not a PNG...
    if (!strPath.EndsWith(".png", 
        StringComparison.OrdinalIgnoreCase) &&
        oImage.Attributes["PNG"] != "true")
    {
      base.Render(writer); // Have control render itself
      return;
    }

    // Setup the filter 
    string strImageUrl = oImage.ImageUrl;
    oImage.Style.Add(HtmlTextWriterStyle.Filter,
      "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
      Page.ResolveClientUrl(strImageUrl) + "', sizingMethod='image')");

    // Replace the image with a 1 x 1 transparent gif
    oImage.ImageUrl = Page.ResolveClientUrl("~/Images/Spacer.gif");
    base.Render(writer); // Have control render itself
  }
}

Items of note:

  • Michael suggests adding sizingMethod='image' to the filter so the image will be displayed at its normal size whether or not you specifically include a width and height.
  • lofty_2 wanted a way other then the file extension to flag a image as a PNG. You may be generating your PNG images dynamically such as charts or graphs, so there may be no extension at all. To solve this, I simply invented a png attribute that you can set to true. Visual Studio will warn that it's not a valid attribute on the Image control, but the control happily collects and stores it in its Attributes collection for us to process in our control adapter.

A Cry From Marketing

I had a strange email from someone in our Marketing department the other day. It said she had received a couple emails from users who couldn't find the download link to download our free security product. That seemed remarkably impossible because nearly every page on our web site contains a huge red "Download Now" button near the top of the page. How can these clowns possibly be missing it?

After taking a walk to think about it, I had a hunch. I asked our Marketing person to find out which browser these customers were using. They were using FireFox or Opera. One customer had even tried both browsers and still insisted there was now download button anywhere on any page. After a little experimentation, I found the problem.

Our big red download button is a PNG image. It seems these customers using FireFox and Opera had changed their user agent string to duplicate precisely the agent string sent by IE 6. My server had no way of knowing the real browser being used, and the control adapter was rendering the IE specific AlphaImageLoader filter that neither FireFox nor Opera bother to acknowledge or display.

Now we can lament and complain about users fiddling with their agent strings like this, but it doesn't change the fact that they do. Nor does it change the fact that they still expect your web site to display perfectly. As you well know, it's never the user's fault that your web site doesn't work right. Even if they are using a toaster oven to browse the internet, it's always your fault!

Closer to Perfect

I ran across a IE "feature" the other day that allows you to put conditionals into the HTML that only IE acknowledges. Some people classify this as a hack, but I'll briefly point out that the usual definition of a "hack" takes advantage of a browser flaw. This new code will use valid IE syntax that Microsoft plans to continue supporting. Having said that, here's how it works:

HTML
<!--[if IE]>
  <span>This html gets displayed only by IE</span>
<![endif]-->
<![if !IE]>
  <span>This html gets displayed by every other browser</span>
<![endif]>

There's not much to explain. The HTML IE will display falls within a standard HTML comment block, so non-IE browsers ignore it. The HTML displayed by non-IE browsers is bounded by tags IE will parse but other browsers will just ignore. Here's the complete details from MSDN. Let's change our control adapter to use these special IE conditionals in the case we are rendering a PNG image.

C#
public class ImageControlAdapter : WebControlAdapter
{
  protected override void Render(HtmlTextWriter writer)
  {
    Image  oImage  = Control as Image;
    string strPath = Page.MapPath(oImage.ImageUrl);

    // If not a PNG...
    if (!strPath.EndsWith(".png", 
        StringComparison.OrdinalIgnoreCase) &&
        oImage.Attributes["PNG"] != "true")
    {
      base.Render(writer); // Have control render itself
      return;
    }

    string strImageUrl = oImage.ImageUrl;

    writer.Write("<![if !lt IE 7]>");
    base.Render(writer); // Used by non-IE & IE7
    writer.Write("<![endif]>");

    oImage.Style.Add(HtmlTextWriterStyle.Filter,
      "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
      Page.ResolveClientUrl(strImageUrl) + "', sizingMethod='image')");
    oImage.ImageUrl = Page.ResolveClientUrl("~/Images/Spacer.gif");

    writer.Write("<!--[if lt IE 7]>");
    base.Render(writer); // Used by IE < 7
    writer.Write("<![endif]-->");
  }
}

The plan is to write this special PNG output to all browsers, not just IE. We'll rely on the browsers rendering engine and the special IE conditionals to display the correct HTML. We no longer need to rely on a valid user agent string. The user can change the agent string all they want, and they will still see PNG images correctly!

To get this to work, though, we need one more change. Our earlier browser file was configured so only IE clients received our special control adapter output. Now we need all client browsers to get this output. This new browser file associates our control adapter to the existing "default" group, that is, all client browsers:

<browsers>
  <browser refID="Default">
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Image" 
               adapterType="ImageControlAdapter" />
    </controlAdapters>
  </browser>
</browsers>

Using the code

The code download contains a small ASP.NET 2.0 web site with the control adapter in the App_Code folder and a default.aspx demonstration page. I've also included a control adapter for the HtmlImage control that's used with the <img ... runat="server"> tag. That's the tag I prefer to use to display images on my pages. Be warned, though, that the control adapter will only be called if you include the runat="server" attribute. Otherwise, the parsing engine just outputs the tag as unparsed HTML.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here