Blurring an image using the HTML 5 canvas

Blog

How to blur an image in the browser as fast as possible

Once in a while I’ll come across a site that can only be described as beautiful. Not well-designed, not just impressive, but beautiful. I frequently go to Midtone Design just to admire it — those icons are a work of art.

Another I visit simply to savour is Mike Matas’s gallery. It’s a joy to play with. Naturally, being a web nerd, I enjoy the site even more because it’s not built with Flash. Mmmmmm, lovely lovely HTML goodness.

I delved into the code to see how it was put together — in fact I was hoping to see something spectacular. You see those images of the next and previous images? The ones in the background, blurry and slightly opaque. I was hoping they were being blurred on-the-fly, in the browser. I was eager to see how it was done.

Alas, they weren’t. Each photo is actually three images: one large, one blurred, and one small-but-focused shown when you drag the scrollbar. So the blurring effect was done in Photoshop.

That got me thinking: how would I go about blurring an image in the browser on-the-fly? Discussing it with a friend, Borgar, we decided it was eminently possible using the fabulously en vogue HTML 5 canvas and a sprinkling of Javascript. I took up the implied challenge and wrote the code. (With a little help: to her eternal shame Alison contributed the linear equation.)

Want to see it in action? Good. Here’s my first attempt at blurring an image with the HTML 5 canvas and Javascript.

What’s the first thing you notice? I’d imagine it’s that it’s incredibly slow. Try spending a few hours knee-deep in code and only then finding that out. I was frustrated: the code was elegant and the resulting image was good, but it was just too slow.

I profiled the code and discovered the biggest reason for the speed, or rather lack of, wasn’t the blurring of the image itself (no more than a simple square filter) but the overhead of my fancy abstraction layer. The pseudo-class/prototype-based programming interface meant my code was theoretically pure, but in Javascript, Theory is merely the pompous and impractical brother to the more successful son, Speed. Theory is to speed as Mycroft is to Sherlock. That’s right, I squeezed a Holmes reference in there.

Anyway, all my superbly-abstracted function calls were slowing the code down — function calls really are quite slow in Javascript.

Onwards to attempt two: remove the abstraction layer and leave myself with some fast but un-pure code. It was faster — about 60% faster in my tests using Safari — but not fast enough. It was still taking around 0.9 seconds for each pass in the filter on my 640x426 image, and for a decent blur you need more than one pass.

To speed it up further I needed to remove as much of the processing from Javascript — which is slow because it’s interpreted by the browser — into native code — which is incredibly fast as it’s compiled. I tinkered here and there but I couldn’t think of many more improvements.

I showed Borgar the code the next day and explained the problems. With a twinkle in his eye he gave me a solution. I was thinking about the problem the wrong way round; I didn’t want to blur the image, I only wanted to give the impression it was blurred. Instead of running the image through a filter algorithm I should let the browser do the hard work. Yes yes, but how? I asked impatiently.

A diagram at least attempting to explain how the faked blur works

Figure 1. The circled numbers show the position of the first pixel of each of the eight semi-transparent copies overlaid on the original image. The dotted line highlights the top left corner of the image.

Here was his solution. Take eight copies of the image and make each one 12.5% (1/8th) opaque. Then overlay each copy on top of the original image, each one offset by one pixel (see figure 1). Not only would this give the impression of a blurred image but the image processing would be done by the browser, thus taking my Javascript-based blur algorithm and turning it into compiled C code.

I think I called Borgar something rude, just to make sure he couldn’t act too smug. He did anyway. That evening I converted the code into a much slimmer, more elegant algorithm. And it was fast. On my 640x426 test image in Safari it took roughly 0.14 seconds per pass. My original algorithm took 2.2 seconds per pass and the code was four times longer.

Damn him. Damn him for being right.

So there we have it. Here’s the code to blur an image using the HTML 5 canvas as fast as possible. If you want to blur an image using only Javascript and the canvas element don’t write a blur algorithm, fake it. Speed is king.

One for the future

Just one more thing. How would you like to blur an image on-the-fly without a single line of Javascript?

It turns out the wonderful behemoth that is SVG has gaussian blurring built in. You can embed a bitmap image within an SVG vector graphic and tell the browser to pass it through a gaussian blur filter, all with a few lines of XML. It works in Chrome (since at least version 5.0), Firefox (version 3.0 and later), Webkit nightlies (January 2010 onwards), and Opera (version 9.0 up), but we’re still waiting on our sulky and ever-late acquaintance, Internet Explorer.

Let me summarise

It’s exciting to see the possibilities lying ahead of us in image manipulation in the browser. The HTML 5 canvas is certainly offering up some potential, and once we as web developers have finished with our manic obsession and settled into a more sensible relationship with the element, we might start flirting with SVG a little more.

Update, 8 March 2012

Sven Groot forked the code to take a slightly different approach that includes being able to choose the intensity of the blur.