Click here to Skip to main content
Click here to Skip to main content

Using a Control Adapter to Properly Display PNG Images in IE

By , 25 Oct 2006
 

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:

<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:

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:

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

We want to output this 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:

<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.

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:

<!--[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.

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

About the Author

Charles Windhausen
Web Developer
United States United States
Member
Charles works at PC Tools in Boulder, CO as a "do everything" developer. He's worked with Windows development for over 10 years and web development for over 5.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralThank you very much !!membereebee23 Mar '09 - 1:27 
Hi Charles,
 
This was very usefull for me, I was looking for 2 weeks already to get a "real" solution for this. All the other solutions I found (javascript based), don't work well with ASP.net.
 
As IE 8 is comming out now, do I need to change the ("<!--[if lt IE 7]>"); clause ?
 
Cheers,
 
Eric
 
eeBee
 
BDS
-->
AnswerRe: Thank you very much !!memberCharles Windhausen23 Mar '09 - 3:21 
Thanks for the Kudoos. Although I haven't tested this code on IE8, it should work unchanged. The code you asked about means "if less than IE 7". Since IE 7 started displaying PNG images correctly, I would hope Microsoft has IE 8 doing the same.
 
Charles
General[Message Removed]memberMojtaba Vali25 May '08 - 1:05 
Spam message removed
GeneralPage.MapPath bugmemberCoolVini7 Jan '08 - 2:16 
The point that you try to locate the file
using the Page.MapPath()
 
have bugs in cases that the source file begins with http://
or starts with ../image/image.gif
or somthing similar.
 
for example this not working.
 
<asp:image id="Image4" imageurl="http://localhost/images/Sample.png" runat="server" />
 

As I see you do not really need to do that conversion, just search for the .png
So remove the Page.MapPath() that have no reason to exist.
If you need to remove any possible staf after the .png, must do some extra code
put I am not recomended. Vary rare to place on image, thinks after the extention
and also this function called all the time, so need to be fast.
 
also a try catch think is importan in case of somthing not work.
 
Thank you for this code.
coolvini
AnswerRe: Page.MapPath bugmemberCharles Windhausen29 Jan '08 - 5:12 
You are absolutly right! I don't need the call to MapPath because we don't care that the image resides on my local server. All we care about is that it ends in ".png". So all readers, please change this line

// If not a PNG...
if (!strPath.EndsWith(".png",
StringComparison.OrdinalIgnoreCase) &&
oImage.Attributes["PNG"] != "true")

to this

// If not a PNG...
if (!oImage.ImageUrl.EndsWith(".png",
StringComparison.OrdinalIgnoreCase) &&
oImage.Attributes["PNG"] != "true")

I'm afraid the call to MapPath was a hold over from the full control adapter I use. I've got an adapter that will automatically add WIDTH and HEIGHT attributes by inspecting the size of the image found on the local server. I didn't include that logic in this article because I wanted to focus on one distinct issue.
 
Thanks,
Charles
Questionperformance with runat="server"memberPeterSmith25 Nov '07 - 0:52 
Hi, great article...im very interested, but doesnt the fact that you add the runat="server" attribute to the image tag have a negative impact on performance?
 
Thanks!
GeneralRe: performance with runat="server"memberCharles Windhausen29 Jan '08 - 5:25 
Sure. Running X+Y lines of code will always take longer than running just X lines of code, but you have to ask yourself two questions:
  1. Do the extra Y lines of code cause a visible performance hit from the user's perspective?
  2. Do the benefits of the added Y code outweigh any performance issues?
Only you can decide that. As for how much of a performance hit this adapter adds, I really don't know. All I know is that it is less than I can perceive. You can, however, reduce the problem by using the <img runat="server" /> tag only on those images you know are PNGs. Then the image adapter only gets run for images you know are PNGs rather than all images.
 
Charles
QuestionGood solution! I have a similar problem...memberroimergarcia11 Nov '07 - 15:22 
Well, I'm working in a web project, with a lot of buttons with png images aligned at left and a caption; All the buttons inherits from BaseButton, and it from System.Web.UI.Controls.Button. I'm developing in IE7, where every thing work good, but when I upload an alpha version for testing I noted the problem with IE6.
 
The image is set using css classes (with other style info), not image controls; so, the html that the browser gets is some thing like:
<input 
     type="submit" 
     name="cmdAceptar" 
     value="Aceptar" 
     id="cmdAceptar" 
     class="BotonAceptar" />
i.e. I can't use a Control Adapter to manipulate the render of the control since the image is being set by css Frown | :( any suggestion?Confused | :confused:
Thanks in advance for any help!

 

 
I think, therefore I exist.
Descarte

Generalproblem with ToolTip attributemembermoldoe4 Aug '07 - 8:11 
There is a small problem with in this solution. I'm using this ImageControlAdapter to render a <asp:ImangeButton> tag.
Adding the style that is suppose to render the PNG properly is breaking the ToolTip ASP attribute on IE. The ToolTip attribute is rendered into a Title html attribute which is not shown on IE because of that specific style added to the input element. On Firefox works fine.
The solution is to set the AlternateText attribute for IE instead of ToolTip. You have to do it in the ImageControlAdapter because if you set both it will not work and AlternateText does not work on Firefox.
 
......
 
writer.Write("<![if !lt IE 7]>");
base.Render(writer); // Used by non-IE & IE7
writer.Write("<![endif]>");
 
oImage.AlternateText = oImage.ToolTip;
oImage.ToolTip = null;
 
......
 
Daniel
NewsPNGImage web control for .net 1.1memberwowitsben27 Jul '07 - 11:18 
For those of you who are interested, some time ago I developed an ASP.NET web control specifically for displaying transparent PNG images in ASP.NET 1.1. It inherits from the Image web control that ships with the .NET framework so it has all the features of the image control. This control will even display a GIF image as an alternative if the web browser can't display an PNG image.
 
You can download the source code and assembly on my website at http://bigbeno.dyndns.org:8000/

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 25 Oct 2006
Article Copyright 2006 by Charles Windhausen
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid