inicio mail me! sindicaci;ón

Brain-Dead Canvas Quirk

The HTML5 Canvas. Great, isn’t it? You can draw pretty much anything you want in a pre-defined area. In more recent browsers, you can even get & set pixel data too, so you pretty much have full control over how things look.

The problem

Unfortunately, the Canvas suffers from a pretty brain-dead design flaw. That is while you can get and set pixels, you cannot control when the pixel data you grab with getPixelData() will be freed from memory. This is exacerbated by the fact that the whole interface runs on top of JavaScript.

Now wait a minute“, you might think “doesn’t JavaScript have garbage collection for this sort of stuff?”. Well, yes. The trouble is, JavaScript has no standardized garbage collection system, so depending on which browser you happen to be using it’s pot luck whether or not your pixel data will get freed on time.

An example

To start off with, here’s a really simple example test case, using “putImageData(getImageData())“.

Now lets use a real example here. A while back, you might recall i was writing a SCUMM interpreter in haXe that just so happened to compile to JavaScript as well. For the SCUMM runtime, i am required to display room graphics overlaid with various objects. The graphics are 8bit and use a palette, and that palette can be changed at runtime.

R - PALETTE - RGB

For performance reasons, i decode the graphics and store them in Canvas elements using only the red channel. In fact, throughout the graphics processing pipeline, the red channel is the only part of the pixel data i use. I also make heavy use of the native blitting functions (e.g. fillRect).

It’s not until i come to display the graphics that i re-map the pixels from the red channel into the final colours using the current palette. This requires me to getImageData() the current pixels in the canvas, iterate through and set the data[], and then putImageData() back to the Canvas for the final result.

The trouble is since i am re-mapping the pixels every frame, memory usage on certain browsers (Opera, Firefox) skyrockets up to a certain threshold – anything up to 2gb depending on what is happening.

For the test case running on OS X, Opera> tops out at ~600mb. Webkit and Firefox both top out at around 1.25gb (honestly though, i just stopped looking as it was getting stupid). It’s not until i either suspend re-draw and wait several seconds, or simply close the browser window that memory usage returns to normal.

Oddly enough when re-mapping the pixels in my SCUMM interpreter (not in the test case), Webkit’s memory usage stays constant. This flies in the face of what one would expect to happen based on the test case. As for why, it could be anything. The smaller canvas size, assignment of variables – who knows.

Workarounds?

Realistic workarounds include:

  • Perform all graphics operations manually on a single set of pixel data (too slow!)
  • Use drawImage() as opposed to getImageData() & putImageData()
  • Limit the amount of screen updates
  • When working with palletised image data that needs re-mapping, store it as RGB, and re-calculate it only when the palette changes
  • Don’t use getImageData() at all, especially considering it doesn’t seem to work in Safari at the moment.

Either that, or simply implement everything which needs pixel-level graphics manipulation in Flash or a Java Applet, foregoing the pure Javascript ethic.

blog comments powered by Disqus