HTML canvas performance

Jump to: navigation, search

HTML 5 has a new Canvas element which provides an API for bitmap drawing operations on a bidimensional surface. Rendering is generally quite fast on desktop Web browsers, and it requires no plugins like Flash or Java.

The problem

On the XO OS 8.2.x, and probably on older versions, HTML Canvas performance is most often very slow because the default Browse activity is configured to scale pages. Typically, pages are rendered using 96 DPI in Web browsers, but on the XO the browser renders the pages using 134 DPI. This ensures the text and images are still readable - otherwise they'd be far too small because the XO display has a high DPI resolution. Nonetheless, scaling images up is quite slow - the high-quality bilinear filter is used. This impacts the overall performance of the browser and moreso the performance of the Canvas element.

The Browse activity uses the xulrunner package, which contains the Gecko layout engine version 1.9.0 (the same as in Firefox 3.0).

Users can change the DPI used for rendering a page by going to about:config, where they can modify the layout.css.dpi value. Yet, Hulahop includes some piece of puzzling code which always resets the layout.css.dpi configuration value to 134.

The xulrunner package includes a patch which alters the page scaling logic in Gecko. This patch makes a simple, yet important change to how the DPI config value is used for scaling the page being rendered. A normal Gecko build only scales pages using an integer scaling factor, but on the XO the scaling factor can also be a floating-point number. This means that a normal Gecko build uses a scale factor of 1 for DPI < 192, and a scale factor of 2 for 192 <= DPI <= 288, and so on.


Gecko 1.9.1 includes a patch which adds a new config option layout.css.devPixelsPerPx. This allows OLPC to configure the browser such that physical units render properly scaled using the correct DPI value, but not the CSS pixel values. CSS pixels could be equal to device pixels - they would all render small, but much faster.

Another Gecko patch worth being noted is the CSS image-rendering property support. This would allow Web developers to tell Gecko to use nearest-neighbour instead of bilinear interpolation for the scaling of elements.


The XO browser has two problems actually: 1) performance issue caused by scaling everything up; 2) the difference in the scaling logic from a normal Gecko build.

Problem 1: Having everything render using 96 DPI is not acceptable - pages would be unreadable. I would suggest that Gecko on the XO scales images using a faster algorithm instead of the bilinear one. It would also be interesting to experiment with the new layout.css.devPixelsPerPx configuration set to 1. Maybe hardware acceleration in newer XOs?

Problem 2: Keeping the current 134 DPI value would always require Gecko to be patched, thus making it different from other Gecko builds. Maybe the browser could use 200 DPI? Perhaps pages would render too big.

A different line of thought would be: "why complain about problem 2?" I mean, Web developers are not supposed to be tinkering with DPI in their Web pages - it's the problem of the browser.

As a Web developer I do not mind about problem 2 if problem 1 is fixed. Problem 2 is important only when trying to work around problem 1.

Work around

It's simple: you need to scale down the Canvas element such that Gecko cancels the scaling. However, you need to find out the DPI used for rendering the page. You can do this using CSS 3 Media Queries only. You need something like:

<!DOCTYPE html>
<html lang="en">
    <style type="text/css"><!--
      @media screen and (resolution: 96dpi) {
        #resInfo { width: 96px }
      @media screen and (resolution: 134dpi) {
        #resInfo { width: 134px }
      @media screen and (resolution: 200dpi) {
        #resInfo { width: 200px }
      @media screen and (resolution: 300dpi) {
        #resInfo { width: 300px }
      #resInfo { display: none }
    <div id="resInfo"></div>
    <canvas id="yourCanvasElement" width="600" height="500">

You can see that the resolution is detectable using CSS 3. We set the DPI to be the element width.

Your JavaScript code can be:

var resInfo = document.getElementById('resInfo');
var cs = window.getComputedStyle(resInfo, null);
var dpi = parseInt(cs.width);
if (isNaN(dpi)) {
  dpi = 96;

In a normal Gecko build you can scale down the Canvas element using the following code:

var scale = Math.floor(dpi / 96); // integers only!
var canvas = document.getElementById('youCanvasElement'); = (canvas.width / scale) + 'px'; = (canvas.height / scale) + 'px';

Simple. Now, on the XO we must calculate the scaling factor differently, and we also have to cope with the fact Gecko 1.9.0 is used (no support for CSS 3 Media Queries). This means we have to do some user agent sniffing:

if (window.navigator.userAgent.indexOf('olpc') != -1) {
  dpi = 134; // hard-coded value, we cannot determine it
  var appUnitsPerCSSPixel = 60; // hard-coded internally in Gecko
  var devPixelsPerCSSPixel = dpi / 96; // 1.3958333333
  var appUnitsPerDevPixel = appUnitsPerCSSPixel / devPixelsPerCSSPixel; // 42.9850746278...

  scale = appUnitsPerCSSPixel / Math.floor(appUnitsPerDevPixel); // 1.4285714285...

Done! The rest of the code doesn't need any changes, just the dpi and the scale variables do.

Note that Gecko has support for floating-point pixel values, so you do not need to worry about these values looking like "doh, do they work?" Luckily, yes, they do. With the above code Gecko will not apply any scaling to the Canvas element.

Also note that the user agent sniffing performed here is merely an example. In the future you will need to adapt the code to take into account new versions of the Browse activity on the XO, versions which will use Gecko 1.9.1+.

Live examples

  • Performance test - this is some minimal code I did to test the performance of this work-around.
  • PaintWeb - the open-source project I am developing. It should be working fine on the XO.

Thanks go to Robert O'Callahan from Mozilla for his valuable feedback. Also thanks to Martin Langhoff for his support and help with finding the OLPC Gecko patches.