Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is putImageData so slow?

I am working with a relatively large Canvas where various (complex) stuff is drawn to. I then want to save the Canvas' state, so I can quickly reset it to the state it now is at a later point. I use getImageData for this and store the data in a variable. I then draw some more stuff to the canvas and will later reset the Canvas to where it was when I saved it's state, using putImageData.

However, it turns out, that putImageData is very slow. Infact, it is slower than simply redrawing the entire Canvas from scratch, which involves several drawImage covering most of the surface, and over 40.000 lineTo operations followed up by strokes and fills.

Redrawing the approx 2000 x 5000 pixel canvas from scratch takes ~ 170ms, using putImageData though takes whopping 240ms. Why is putImageData so slow compared to redrawing the canvas, although redrawing the canvas involves filling nearly the entire canvas with drawImage and then again filling roughly 50% of the canvas with polygons using lineTo, stroke and fill. So basicly every single pixel ist touched at least once when redrawing.

Because drawImage seems to be so much faster then putImageData (after all, the drawImage part of redrawing the canvas takes less than 30 ms). I decided to try to save the state of the canvas not using getImageData, but instead using canvas.toDataURL and then creating an Image from the data URL which I would stick into drawImage to draw it to the canvas. Turns out this whole procedure is much faster and only takes roughly 35ms to complete.

So why is putImageData so much slower then the alternatives (using getDataURL or simply redrawing)? How could I speed things up further? Is there and if, what is in general the best way to store the state of a canvas?

(All the numbers are measured using Firebug from within Firefox)

like image 588
Daniel Baulig Avatar asked Oct 17 '10 10:10

Daniel Baulig


3 Answers

Just a small update on what the best way is to do this. I actually wrote my Bachelor Thesis on High Performance ECMAScript and HTML5 Canvas (pdf, German; password: stackoverflow), so I gathered some expertise on this topic by now. The clearly best solution is to use multiple canvas elements. Drawing from one canvas onto another canvas is just as fast as drawing an arbitrary image to a canvas. Thus "storing" the state of a canvas is just as fast as restoring it later again when using two canvas elements.

This jsPerf testcase shows the various approaches and their benefits and drawbacks very clearly.

Just for completeness, here how you really should do it:

// setup
var buffer = document.createElement('canvas');
buffer.width = canvas.width;
buffer.height = canvas.height;


// save
buffer.getContext('2d').drawImage(canvas, 0, 0);

// restore
canvas.getContext('2d').drawImage(buffer, 0, 0);

This solution is, depending on browser, up to 5000x faster than the one getting the upvotes.

like image 67
Daniel Baulig Avatar answered Oct 23 '22 21:10

Daniel Baulig


In Firefox 3.6.8 I was able to workaround the slowness of putImageData by using toDataUrl/drawImage instead. For me it's working fast enough that I can call it within handling a mousemove event:

To save:

savedImage = new Image()
savedImage.src = canvas.toDataURL("image/png")

The to restore:

ctx = canvas.getContext('2d')
ctx.drawImage(savedImage,0,0)
like image 11
Corey Trager Avatar answered Oct 23 '22 22:10

Corey Trager


Firstly you say you are measuring with Firebug. I actually find that Firebug slows down JS execution considerably, so you may not be getting good numbers for performance.

As for putImageData, I suspect it's because the functions takes a large JS array containing many Number objects, all of which have to be checked for range (0..255) and copied into a native canvas buffer.

Maybe once the WebGL ByteArray types are available, this sort of thing can be made quicker.

It does seem odd that base64 decoding and uncompressing the data (with the PNG data URL) is quicker, but that only calls one JS function with one JS string, so it is using mostly native code and types.

like image 2
andrewmu Avatar answered Oct 23 '22 21:10

andrewmu