In this post I had described how I was able to improve performance of animation in HTML5 Canvas by using a backup Canvas. The trick was to draw static part of the main Canvas on the backup Canvas and whenever any object moves (during animation), draw the content of backup Canvas on to the main Canvas first and then draw moving objects. I had optimised this by copying only a part from the backup Canvas that was exposed by the moving object. This gave me much better animation performance than redrawing scene every time.
Though the animation was better, I was still not happy with the performance. It worked fine on small devices like phones, but animation was still not very smooth on tablets. So I started looking for ways to improve it further.
I had looked at getImageData and putImageData methods earlier, but this combination had not worked for me. getImageData returns ImageData object that contains pixel information (RGB and other pixel display properties) of the Canvas. putImageData can be used to draw ImageData back to the Canvas.
I thought writing pixel information directly to the Canvas should be faster than copying data from backup Canvas to the main Canvas. But putImageData was not displaying anything on the Canvas in my testing. It turned out that I was calling this function with incorrect arguments.
The signature of putImageData is as follows -
Arguments dirtyX,dirtyY,dirtyWidth,dirtyHeight defines rectangle from the image data to be put on the Canvas. Arguments x and y refer to co-ordinates where you want to put the image data on the Canvas. I was setting value of x an y to the top-left of dirty rectangle on the main Canvas e.g.
Consider two objects on the main Canvas. One is a rectangle and another one is a circle. The rectangle is overlapping circle, so the circle is not visible. If we move the rectangle from position p1 to p2, the circle should become visible. So just before the rectangle is moved, I paint everything else (in this case it is the circle) on the backup Canvas and get image data of the entire backup Canvas, by calling getImageData(0,0,canvasWidth,canvasHeight).
When the rectangle moves to position p2, its earlier position, p1 becomes dirty. So I need to copy content of dirty rectangle from the backup Canvas and paint on the main Canvas.
I have image data of the backup Canvas. The mistake I was doing when putting image data of dirty rectangle (p1) on to the main Canvas was that I was passing x1 and y1 (see above figure) as position on the main Canvas where image data to put (second and third argument to putImageData). Actually x and y values of putImageData are offset where the entire image data to be put. So if you do not want to move the image from the backup Canvas, and if you getImageData from the backup Canvas from offset 0,0 then you must putImage from offset 0,0 on the main Canvas. I updated the animation logic as follows -
- Create a backup Canvas element in the document.
- Just before an image is to be moved on the main Canvas, draw everything from the main canvas to the backup canvas, except the image to be moved.This content (on the backup Canvas) is not going to change during animation.
- Get imageData of the entire backup Canvas
- When the image is moved, putImageData of dirty rectangle on to the main Canvas. This will expose previously hidden object under the image that is being moved. Then I draw the moving image on the main Canvas
With above changes the animation worked much smoother in my application.