Graphics Programming Environment Shootout Episode 3: JavaScript & Canvas

This article is the third in a series in which I implement the same app in several different graphical programming environments. Today’s contender: JavaScript & canvas.

Canvas is one of the new APIs introduced in the flurry of specifying activity by the W3C that got bundled together under the HTML5 monicker. A canvas is a rectangular area of the HTML page that contains a raster image which can be manipulated using JavaScript. Unlike the other graphical environments covered so far, canvas is a specification that is implemented by multiple browsers, each of which has its own performance characteristics.

“imagine” in JavaScript & canvas

You can view the implementation of “imagine” in JavaScript & canvas and check out the source code here

Performance: On my Intel Core i7 MacBook, the frame rates with 8000 sparks on screen were 4.3 fps in Firefox 7, 6.2 fps in Chrome 15 and 6.0 fps in Safari 5.1. These figures are pretty terrible compared to the other environments I’ve covered so far, but there is a good explanation: canvas anti-aliases all lines, which precludes a performance optimisation that was possible in both Flash and Processing.

Anti-aliasing is a useful technique for producing smooth edges on vector graphics. This image is magnified 600% to make the individual pixels clearer:

Anti-aliasing make shapes appear smooth on a limited resolution display

Anti-aliasing is computationally expensive however. The Processing and Flash versions of imagine turn off antialiasing and replace it with an alternative technique – as long as the rendered shape is moving at a roughly constant rate, simply making the shape semi-transparent will produce a smoothing effect:

Poor man’s anti-aliasing works just as well as the real thing in certain circumstances

So this performance comparison is not exactly fair. I made another version of the canvas app that uses squares instead of circles. Squares are considerably faster to draw, and the performance for this version is in line with the Flash and Processing versions.

Programming with JavaScript & canvas

In the last two episodes I’ve provided a port of the simplest animated graphical app I could conceive of. I noted that the ActionScript version was around twice as long because of the overhead of setting up a raster drawing environment in a general purpose drawing framework.

Well the JavaScript version is almost as long as the Flash version, but not for exactly the same reason:

window.onload = function() {
  var canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  canvas.width = 400;
  canvas.height = 400;
  var ctx = canvas && canvas.getContext("2d");
 
  var bg = "#FF0088";
  document.body.onclick = reset;
  reset();
  setInterval(draw, 50);
 
  function draw() {
    var radius = random(5, 50);
    ctx.beginPath();
    ctx.arc(random(-100, 500), random(-100, 500),
        radius, 0, Math.PI * 2, false);
    ctx.closePath();
 
    ctx.strokeStyle = "#000";
    ctx.lineWidth = 1;
    ctx.fillStyle = "#FFF";
    ctx.fill();
    ctx.stroke();
  }
 
  function reset() {
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
  }
 
  function random(from, to) {
    return from + Math.random() * (to - from);
  }
}

Check out the the running application here.

Two things make this sample longer than the Processing version.

Firstly, like Flash’s raster drawing functionality, canvas is just a small part of the total functionality offered by HTML environment. A few lines are therefore dedicated to setting up the environment for animated raster drawing. This makes it harder for a beginner to get started with canvas, though finding code samples for canvas on the net is easier than for the Flash equivalent.

Secondly, the actual drawing code inside draw() is longer. Common drawing operations are split into calls to geometry methods like arc() which describe a path, and painting methods like stroke() that render the most recently described path. I’m sure the guys at Apple had their reasons for designing the API the way they did, but for basic operations it means that you have more lines of code for the same effect.

If you’re intending to write a lot of drawing code, you’d be well advised to factor out the verbose canvas API into some simple drawing methods. This code for example, will replace 8 lines of drawing code with a one line method call:

function drawFilledCircle(x, y, radius, strokeColor, fillColor) {
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2, false);
  ctx.closePath();
  ctx.strokeStyle = strokeColor;
  ctx.lineWidth = 1;
  ctx.fillStyle = fillColor;
  ctx.fill();
  ctx.stroke();
}

This is something that the JavaScript community is quite good at, since JavaScript has a history of clunky APIs that need wrapping to make usable. The W3C DOM wrapped in jQuery springs to mind.

Conclusion

Despite having a limitation that made it slow for imagine, canvas is a flexible and capable raster drawing API, but one that really needs to be wrapped in a library for maximum usability.

Next up: Google NaCl (coming soon)