“Grayscaling” in non-IE browsers

This started out as a little experiment and eventually turned into quite an endeavor. The task was simple enough; to emulate Internet Explorer’s ‘grayscale‘ filter in all non-IE browsers. The solution, much to my initial surprise, is not as tricky as you would think.

The ‘grayscale‘ filter in IE can be applied to any element and visually transforms the element itself into grayscale. You can apply the filter using one line of messy proprietary CSS:

elem.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)';

This can also be defined in your StyleSheet:

elem {
    filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
    /* Element must "hasLayout"! */
    zoom: 1;
}

As shown, getting this to work in IE is a piece of cake; other browsers, however, require much more attention!

There are two things to consider; images and everything else. “Everything else” is quite simple; loop through all elements within the document and look for colour properties such as ‘backgroundColor’ and ‘color’, then convert their RGB values to grayscale. There are a few ways of doing this; note that we’re not talking about desaturating a photo; “grayscaling” is slightly different (as I understand it):

// Desaturate:
function RGBtoDesat(r,g,b) {
    var average = (r + g + b) / 3;
    return {
        r: average,
        g: average,
        b: average
    };
}
 
// Grayscale:
function RGBtoGrayscale(r,g,b) {
    var mono = parseInt( (0.2125 * r) + (0.7154 * g) + (0.0721 * b), 10 );
    return {
        r: mono,
        g: mono,
        b: mono
    };
}

So, each element with a colour property has it converted to grayscale; the original colour is stored somewhere for resetting purposes.

Whether our image can be converted to grayscale depends on two things; the browser in question must support the HTML5 canvas element and its ‘getImageData’ method and the image must be hosted on the same domain; externally hosted images cannot be passed into ‘getImageData’ regardless of whether it’s supported. Google Chrome and Safari (<4) don’t support ‘getImageData’ so we’re stuck there, but, on other browsers that support the canvas element “grayscaling” images can be achieved!

The only way to do this is to “manually” traverse all pixels of the image in question and apply the same ‘RGBtoGrayscale’ function as we did for the CSS colour properties. This can really eat up the browser; even speedy JavaScript engines can suffer considerably with large images.

For the reason mentioned above it makes sense to add a ‘prepare’ function to run before anything actually needs to be “grayscaled” – this function can use the classic zero-timeout recursion technique so as not to lock up the browser. If only small images need to be converted then you can avoid using ‘prepare’ and go straight ahead with the brute-force conversion.

Why, oh why?

You may wonder what the point is in “grayscaling” anything… Well, for one; eliminating colour detracts focus from the user therefore leaving their attention open for other focus-grabbing items on your website; e.g. a lightbox. Forum software such as vBulletin makes it so that the page goes entirely grayscale when you click logout; this brings up a confirm box which is quickly and easily identifiable since it’s the one of the only things with colour left on the screen.

The real reason behind this whole “grayscaling” obsession is that I was curious about whether it’d be possible to achieve; I knew about the filter in IE and wondered if other browsers could be made to emulate this handy effect. I know the effect itself might be considered out-of-date but I really don’t care; I was only interested in getting it to work.

Demo

For those blood-thirsty demo hunters lurking around I’ve created a demo page which shows the functionality as described in this post. Remember, it won’t work properly in Safari (<4) or Chrome (and probably some old version (pre v.2) of FF); also remember it’s just an experiment!

The DEMO: /demos/grayscale/

Usage

To “grayscale” an element you need to call grayscale() with that element passed as a reference, e.g.

var el = document.getElementById( 'myEl' );
grayscale( el );
 
// Alternatively, pass a DOM collection
// (all elements will get "grayscaled")
grayscale( document.getElementsByTagName('div') );
 
// Even works with jQuery collections:
grayscale( $('div') );

To reset an element (back to its original colourful state) you must call grayscale.reset() and pass whatever elements you want reset:

grayscale.reset( el );
// reset() also accepts DOM/jQuery collections
grayscale.reset( $('div') );

The ‘prepare’ function, as discussed earlier, should be called when there’s a large image to process or even if there are several smaller images. Be aware that larger images will take quite a while to process (just a 300×300 PNG takes about 3 seconds in ‘prepare’ mode).

grayscale.prepare( document.getElementById('myEl') );
// Also accepts DOM/jQuery collections
grayscale.prepare( $('.gall_img') );

Looking for this article in German? Thanks Christopher!

Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!