Subscribe to RSS feed

Posts tagged with "performance"

HTML5 canvas performance: Drawing circles

, , ,

HTML5 canvas baubles on a Christmas tree

As it's nearly Christmas, I was playing with HTML5 canvas to draw baubles on a photo of a Christmas tree. Wondering what was the best way to do it, I came across this answer on Stack Overflow about drawing circles with just radial gradients.

Circles

As you probably know, the standard way of drawing circles is to use arc():

// Drawing a circle the traditional way
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, true);
ctx.fillStyle = 'rgba(195, 56, 56, 1)';
ctx.fill();
ctx.closePath();

This way of drawing a circle is a bit cumbersome in my opinion, compared to SVG for example. I thought the idea of using just radial gradients was a clever alternative and wondered what the performance difference would be.

// Drawing a circle with a radial gradient
var gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0.95, 'rgba(195, 56, 56, 1)');
gradient.addColorStop(1, 'rgba(195, 56, 56, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(x - radius, y - radius, x + radius, y + radius);

Sure enough, using radial gradients is slower than arc(). Several times slower! You can play with this canvas test page here to see the speed difference for yourself.

If I'd thought about it properly, I should have realised this without needing to test it and saved myself some time, but then I tried playing with spheres (well, circles with shading) as well.

Spheres

There are two common ways to make spheres in canvas:

  1. Radial gradients
  2. Existing images using drawImage()
// Drawing a sphere with radial gradients
var gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.2, 'rgba(255, 85, 85, 1)');
gradient.addColorStop(0.95, 'rgba(128, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(128, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(x - radius, y - radius, x + radius, y + radius);
// Drawing a sphere with an existing image
var img = new Image();
img.src = 'images/baubles.png';
ctx.drawImage(img, x, y, width, height);

As before, radial gradients are several times slower. Of course, the flip side is that radial gradients are generated dynamically and so can be changed on-the-fly with JavaScript, whereas images have to be pre-made in graphics software. These images can't be edited directly with JavaScript although you can easily change their size. You can also control the colour in a couple of ways:

  1. Using an image sprite of a particular image with varying colours.
  2. Using a greyscale image and applying a semi-transparent overlay with arc().

Don't forget that using images means they have to be downloaded first so it's better to pre-load them if possible.

You can test the performance of these on the same canvas test page.

As you can see, the overlay approach is obviously slower but not as much as with gradients. It also gives you more freedom in controlling the colours, however the overall effect has lower contrast than the original image.

Summary

In general, the speed difference is not noticeable for simple applications or fast hardware, but could be an issue if you're using animation, making a high-performance game, or designing for a TV or set-top box. As always, every decision is a compromise so here's a summary of the various trade-offs and what I've learned:

  • If you just want to draw a circle, use arc().
  • If you want to draw a sphere, use an image (and pre-load it).
  • If you want to draw a variety of spheres, try using image sprites.
  • If you want spheres with dynamically-changing colours, consider using an image with a semi-transparent overlay.
  • Only use radial gradients if you really need to.

One final thing I've learned is that adding thousands of baubles does not enhance a Christmas tree's beauty!

UPDATE:

Marcelo came up with the cool idea of creating a single image on a hidden canvas using a radial gradient, and then repeatedly drawing that with drawImage(). This gets around the need to create images in advance and also means you can edit the colour on-the-fly. But here's the best part — ignoring the initial gradient creation time, it's actually faster than using drawImage() on an existing image! The code looks something like this:

// Create a second "buffer" canvas but don't append it to the document
var tmpCanvas = document.createElement('canvas');
var tmpCtx = tmpCanvas.getContext('2d');

// Add the necessary gradients here, as above

// Draw the image from the second "buffer" canvas
ctx.drawImage(tmpCanvas, x, y, width, height);

So, if you're drawing lots of circles or spheres, this is my recommended method. Nice one Marcelo!

Why use @supports instead of Modernizr?

, , , ...

When Chris Mills published his dev.Opera article Native CSS feature detection via the @supports rule, @silvenon asked me "But wouldn't that be redundant? Modernizr already does a feature detection whether or not the browser is old."

The reason to use @supports over Modernizr is performance; functionality that's built into the browser will always be faster than adding it in script. Removing an external dependancy saves an HTTP request to download Modernizr and doesn't require time to execute the JavaScript.

This is one reason why Paul Irish has said "an upcoming release of Modernizr will defer to the results of @supports if @supports is supported".

So, for example, if you're only using Modernizr to test for CSS support, you could conditionally load the JavaScript library if @support isn't available in the user's browser:

if ( !(window.supportsCSS || (window.CSS && window.CSS.supports) )) load_modernizr()

(This release of Opera uses supportsCSS. After implementation, the spec was changed to CSS.supports instead, hence using both in the test above.)

You'd need to write two sets of rules, one using the classes applied by the Modernizr tests, the others using @supports blocks which, in time-honoured CSS fashion, are ignored by browsers that don't understand them. This makes your CSS larger, but saves an HTTP request and saves execution time.

Should you use Modernizr or @supports? The answer is definitively "it depends". As with all web projects, only you can decide which is the best way.

OperaDriver, Test Web Pages Without Efforts

, , , ...

About one year ago, Andreas Tolf Tolfsen announced OperaWatir, a library for driving the Opera browser. To use the library, you needed to brush up a few scripts in Ruby and start testing. Your first thought is

Hey I drive the Web myself. In manual mode! I do not need assistance. What the heck… a driver!

Well, your perspective might change when the Project manager comes to you and says:

We are going to prod in two days. You have to test the 100 Web pages. Ah by the way… I meant all browsers.

Oops… A few drops of sweat later and a few searches on Google, you found Selenium, the mothership of all Web testing. Selenium has developed a protocol to help implementers to develop specific Web drivers emulating or connecting to the browsers UI.

So over the last one year, a few updates have been made:

  • Opera has released the source code of its Web driver.
  • OperaDriver 0.8.1 (see also version 0.8) was released last week introducing the ability to parallelized testing on all OSes. You can now run multiple instances of Opera/OperaDriver simultaneously.
  • There are only two drivers in the stack which are maintained by their own company: OperaDriver and ChromeDriver.
  • OperaDriver's performance has improved significantly. In fact, we are already fast, because our driver is directly plugged to the core engine.
  • But in addition to that, the WebDriver is really emulating what a user would do by interacting with the browser.
  • With the WebDriver API, we are implementing the Selenium API. This makes you not put in any extra efforts for testing with Opera.

A W3C for Browser Testing and Tools Working Group is being discussed. You can read the charter and join. The first F2F meeting will be in London in January.

So now, you can use OperaDriver and finish your project ready to go on production in two days. Go. Start. Play.