PNG-24 Alpha Transparency with MSIE

PNG-24 images with alpha transparency display with a solid gray color as the background by default in Windows versions of Microsoft Internet Explorer. This is because MSIE does not natively support the multiple levels of transparency. The browser does however support the alpha-channel transparency through proprietary filters. For more information on the PNG image format, be sure to check out this site.

Alternative That Eases Site Design

Sure, this takes care of the whole PNG transparency issue, however, there are a lot more CSS-type bugs in Internet Explorer that makes web design harder on web developers.

I ran into a solution when putting this site's new design together with CSS. By using Dean Edward's IE7, you can fix up quite a few of the MSIE quirks for CSS. As of version 0.7, it is an all-javascript solution, so it may not be exactly what you are looking for, but you may want to at least check it out and see what it can do!

The IE7 solution does conflict with my PHP solution to the Internet Explorer alpha-transparency bug. The defaults for IE7 actually cause PNG images fixed with this method not to render at all.

Normal display of PNG Alpha Transparency with MSIE

PNG-24 image with transparency This PNG-24 image with alpha transparency is displayed without any filters or special CSS styles applied, it is simply a normal HTML IMG tag. If you are using Microsoft Internet Explorer (MSIE) on a Windows platform, you will notice that the image has a gray background, showing you the boundaries of the image clearly.

Frustrated Web Designers and Developers

This is a common problem faced by many web designers and web developers. As web technologies progress, web developers want to take advantage of them to make it easier to implement design elements that would otherwise be quite complex - or impossible. However, if popular web browsers don't have these technologies implemented correctly, designers and developers become frustrated when they aren't able to use the best solution (or only solution in many cases) for their problem.

Solutions

Unfortunately, in order for these images to display as intended, some kind of extra code must be applied to the HTML that would otherwise be used to present the images. There are a variety of ways to add the code for these filters that MSIE needs to display the multiple degrees of transparency in the PNG-24 images.

The first method that a developer will likely try is to include the code statically into the HTML code. When doing so, you may have an HTML IMG tag that looks like the following:

<img src="test.png" width="247" height="216" style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='test.png', sizingMethod='scale');" alt="">

This doesn't work. MSIE will do the transform, but still places the unfiltered image on top of it. If the developer then changes the IMG SRC to spacer.png, it works in IE only.

The next step a developer will likely take is to go to their favorite search engine and search for "png transparency ie" in hopes of finding a quick fix. (Afterall, if you are a developer, that is likely how you found this page.) The next types of solutions you may find use JavaScript that do an image substitution, but if the user has JavaScript disabled, these solutions wouldn't do help any. There are also solutions that exploit a bug (or lack of support) in MSIE's CSS implementations to correctly display these PNG images.

Some of these methods may also have undesireable results in other popular web browsers. By creating a PHP function that replaces the necessary parts of an image tag and uses image substitution, MSIE5+ will display the PNG-24 image's alpha transparency as expected - without effecting the display for other browsers.

Fixed display of PNG Alpha Transparency

PNG-24 image with transparency This PNG-24 image with alpha transparency (the same image as used at the top of this page) is displayed by using the PHP function mentioned above. If you are using Microsoft Internet Explorer (MSIE) on a Windows platform, you will notice that the gray background shown in the first instance of this image is now gone.

Checking PNG Transparency

By keeping the above paragraph short, and applying a background color to the title of this paragraph, you should now be able to see a colored bar disappear behind the image and its drop shadow. Notice that the shadow for the image looks as if it has been placed on top of the color bar. In order to have this effect with a GIF or PNG-8 image with binary transparency, you'd have to make the image with the color bar in its background, or a ghosted edge would be visible.

As you can start to imagine, the ability to have multiple levels of transparency in an image allows for nearly endless possibilities for effects on the web. For those of you who have used graphics programs like Photoshop, Paint Shop Pro, and The GIMP, imagine having a way to place a graphic layer on top of another element (using CSS positioning) and then adjusting the transparency of that layer (using PNG-24 alpha-channel transparency).

Methodology

The method used on this page is the same that is used for many JavaScript-based solutions to PNG-24 transparency for MSIE. The PHP script searches for IMG tags in the HTML that have ".png" (case-insensitive) in the SRC attribute, replaces it with a 1px x 1px PNG-8 image containing binary transparency (which MSIE does support) and adds the necessary CSS style statements for MSIE5+ to render the transparency as desired. However, since this is a server-side solution, you do not have to rely on the user's browser settings or capeabilities.

In addition to scanning IMG tags, this function will also scan INPUT tags and search for background images defined with background-image: url(image.png); or background-image: url('image.png');. With background images, there is no need to use an additional transparent image as a placeholder.

Implementation

For this function to work, you will need to be able to capture the web page's browser output into a PHP variable. To accomplish this, simply follow the steps below, and everything should work as expected.

  1. Save the function into a file named "replacePngTags.php"
  2. At the top of the files that you want to use this function with, paste the following code:
    <?php ob_start(); ?>
  3. Now, at the bottom of this file, paste the following lines of code:
    <?php
        include_once 'replacePngTags.php';
        echo replacePngTags(ob_get_clean());
    ?>
    You should also realize that if the replacePngTags.php is not found within your server's include path for PHP, you will have to adjust the include line.
  4. If the file in question is not a file that is already being parsed as PHP, you will have to make an adjustment. For this code to work, the web server needs to see this as a PHP file, or the code will simply display on the page. To do this, you have a couple options:
    • Change the file's extension from .html (or other) to .php or something that your web server will parse. This will depend on your server's configuration.
    • Another option is to tell the server to parse the file extension as PHP. There are drawbacks to this, but I won't get into that here. For the Apache Web Server, this can be accomplished by adding a file called ".htaccess" in the directory with the following line in it:
      AddType application/x-httpd-php .html
      Not all Apache web servers will have the option of using .htaccess files. You may need to contact your hosting provider to determine this. For other web server software, you are on your own (I only use Apache), so if you aren't running the server yourself, contact your provider for options.

Source Code

To see how this is working, you can view the source to the function below, or download the Full Source Download of this page to see how it was all done. Comments or suggestions welcome.

<?php
/*
KOIVI PNG Alpha IMG Tag Replacer for PHP (C) 2004 Justin Koivisto
Version 2.0.5
Last Modified: 2/15/2005

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.

    This library is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this library; if not, write to the Free Software Foundation,
    Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

    Full license agreement notice can be found in the LICENSE file contained
    within this distribution package.

    Justin Koivisto
    justin.koivisto@gmail.com
    http://www.koivi.com
*/

/*
*   Modifies IMG and INPUT tags for MSIE5+ browsers to ensure that PNG-24
*   transparencies are displayed correctly.  Replaces original SRC attribute
*   with a transparent GIF file (spacer.png) that is located in the same
*   directory as the orignal image, and adds the STYLE attribute needed to for
*   the browser. (Matching is case-insensitive. However, the width attribute
*   should come before height.
*
*   Also replaces code for PNG images specified as backgrounds via:
*   background-image: url(image.png); or background-image: url('image.png');
*   When using PNG images in the background, there is no need to use a spacer.png
*   image. (Only supports inline CSS at this point.)
*
*   @param  $x  String containing the content to search and replace in.
*   @param  $img_path   The path to the directory with the spacer image relative to
*                       the DOCUMENT_ROOT. If none os supplied, the spacer.png image
*                       should be in the same directory as PNG-24 image.
*   @param  $sizeMeth   String containing the sizingMethod to be used in the
*                       Microsoft.AlphaImageLoader call. Possible values are:
*                       crop - Clips the image to fit the dimensions of the object.
*                       image - Enlarges or reduces the border of the object to fit
*                               the dimensions of the image.
*                       scale - Default. Stretches or shrinks the image to fill the borders
*                               of the object.
*   @result Returns the modified string.
*/
function replacePngTags($x,$img_path='',$sizeMeth='scale'){
    
$arr2=array();
    
// make sure that we are only replacing for the Windows versions of Internet
    // Explorer 5+
    
$msie='/msie\s(5\.[5-9]|[6-9]\.[0-9]*).*(win)/i';
    if( !isset(
$_SERVER['HTTP_USER_AGENT']) ||
        !
preg_match($msie,$_SERVER['HTTP_USER_AGENT']) ||
        
preg_match('/opera/i',$_SERVER['HTTP_USER_AGENT']))
        return 
$x;

    
// find all the png images in backgrounds
    
preg_match_all('/background-image:\s*url\(\'?(.*\.png)\'?\);/Uis',$x,$background);
    for(
$i=0;$i<count($background[0]);$i++){
        
// simply replace:
        //  "background-image: url('image.png');"
        // with:
        //  "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(
        //      enabled=true, sizingMethod=scale src='image.png');"
        // I don't think that the background-repeat styles will work with this...
        
$x=str_replace($background[0][$i],'filter:progid:DXImageTransform.'.
                
'Microsoft.AlphaImageLoader(enabled=true, sizingMethod='.$sizeMeth.
                
' src=\''.$background[1][$i].'\');',$x);
    }

    
// find all the IMG tags with ".png" in them
    
$pattern='/<(input|img)[^>]*src=(\\\'|\\")([^>]*\.png)\2[^>]*>/i';
    
preg_match_all($pattern,$x,$images);
    for(
$num_images=0;$num_images<count($images[0]);$num_images++){
        
// for each found image pattern
        
$original=$images[0][$num_images];
        
$quote=$images[2][$num_images];
        
$atts=''$width=0$height=0$modified=$original;

        
// We do this so that we can put our spacer.png image in the same
        // directory as the image - if a path wasn't passed to the function
        
if(empty($img_path)){
            
$tmp=split('[\\/]',$images[3][$num_images]);
            
$this_img=array_pop($tmp);
            
$img_path=join('/',$tmp);
            if(empty(
$img_path)){
                
// this was a relative URI, image should be in this directory
                
$tmp=split('[\\/]',$_SERVER['SCRIPT_NAME']);
                
array_pop($tmp);    // trash the script name, we only want the directory name
                
$img_path=join('/',$tmp).'/';
            }else{
                
$img_path.='/';
            }
        }else if(
substr($img_path,-1)!='/'){
            
// in case the supplied path didn't end with a /
            
$img_path.='/';
        }

        
// If the size is defined by styles, find them
        
preg_match_all(
            
'/style=(\\\'|\\").*(\s?width:\s?([0-9]+(px|%));).*'.
            
'(\s?height:\s?([0-9]+(px|%));).*\\1/Ui',
            
$images[0][$num_images],$arr2);
        if(
is_array($arr2) && count($arr2[0])){
            
// size was defined by styles, get values
            
$width=$arr2[3][0];
            
$height=$arr2[6][0];

            
// remove the width and height from the style
            
$stripper=str_replace(' ','\s','/('.$arr2[2][0].'|'.$arr2[5][0].')/');
            
// Also remove any empty style tags
            
$modified=preg_replace(
                
'`style='.$arr2[1][0].$arr2[1][0].'`i',
                
'',
                
preg_replace($stripper,'',$modified));
        }else{
            
// size was not defined by styles, get values from attributes
            
preg_match_all('/width=(\\\'|\\")?([0-9%]+)\\1?/i',$images[0][$num_images],$arr2);
            if(
is_array($arr2) && count($arr2[0])){
                
$width=$arr2[2][0];
                if(
is_numeric($width))
                    
$width.='px';
    
                
// remove width from the tag
                
$modified=str_replace($arr2[0][0],'',$modified);
            }
            
preg_match_all('/height=(\\\'|\\")?([0-9%]+)\\1?/i',$images[0][$num_images],$arr2);
            if(
is_array($arr2) && count($arr2[0])){
                
$height=$arr2[2][0];
                if(
is_numeric($height))
                    
$height.='px';
    
                
// remove height from the tag
                
$modified=str_replace($arr2[0][0],'',$modified);
            }
        }
        
        if(
$width==|| $height==0){
            
// width and height not defined in HTML attributes or css style
            
if(file_exists($_SERVER['DOCUMENT_ROOT'].$img_path.$images[3][$num_images])){
                
// image is on this filesystem, get width & height
                
$size=getimagesize($_SERVER['DOCUMENT_ROOT'].$img_path.$images[3][$num_images]);
                
$width=$size[0].'px';
                
$height=$size[1].'px';
            }
        }
        
        
// end quote is already supplied by originial src attribute
        
$replace_src_with=$quote.$img_path.'spacer.png'.$quote.' style="width: '.$width.
            
'; height: '.$height.'; filter: progid:DXImageTransform.'.
            
'Microsoft.AlphaImageLoader(src=\''.$images[3][$num_images].'\', sizingMethod='.
            
$sizeMeth.');"';

        
// now create the new tag from the old
        
$new_tag=str_replace($quote.$images[3][$num_images].$quote,$replace_src_with,
            
str_replace('  ',' ',$modified));
        
// now place the new tag into the content
        
$x=str_replace($original,$new_tag,$x);
    }
    return 
$x;
}
?>

Search Engine Concerns

Somebody had emailed me about how using this code may effect search engine rankings on Google. The had referred to this article. The part worth quoting from that article follows:

Obviously not all hidden text is "bad." For example, meta tags are hidden, but you'll not be penalized for using them. However, Google does try to penalize pages that use certain other techniques intended to hide text or hyperlinks from the end user while making them visible to the search engine. One common tactic to hide a link is to use a tiny, 1-pixel image that contains a link. Considering that Google already has the technology to index image content, it is a fair bet that you will be penalized for using such a common technique, or will be penalized in the very near future.

Rather than hiding links to pages, consider creating a site map page that contains links to all your other optimized pages. You'll need to link to this page from your home page for Google to consider it as a legitimate sub-page. In my opinion, you should avoid using any images for this link that are smaller than what would be considered a small button on a Web site. So long as the image is of sufficient size and does not consist solely of transparent pixels, Google would have great difficulty penalizing it in any kind of automated process.

Technically, you could still make any image, or a portion of a larger image on your site serve as a link to your site map page. Therefore, a clever Webmaster could still effectively conceal one or more links from the average human visitor without being caught by Google's new spam checks. However, you should be very careful not to simply create GIF images that contain nothing but transparent pixels. If you do, you will surely be red-flagged for spamming. Even if your site is still listed today, now is the time to make sure it is "clean" before Google completes all of its new spam tests.

Some of the clever ways to hide keyword TEXT on a page will likely be targeted as well, so bear this in mind! The best thing to do is to design your pages in such a way that hiding links and keywords becomes unnecessary.

Just to clear up some confusion, this method of displaying PNG alpha transparencies should have no effect on Google's SERPS. Why? Simply because Google's spider (googlebot) doesn't identify itself as a Window's version of Microsoft Internet Explorer 5.5+. This method only changes the code for those browsers, not for others. To see this, view this page with IE and view source. Then open up Mozilla (or other browser) and compare the source for that page against the one for IE.

Updates


Warning: include_once(site_menu.php) [function.include-once]: failed to open stream: No such file or directory in /share/www/ie-png-transparency/index.php on line 183

Warning: include_once() [function.include]: Failed opening 'site_menu.php' for inclusion (include_path='.:/usr/share/php5:/usr/share/php') in /share/www/ie-png-transparency/index.php on line 183