I have been working on an application that performs some animation in HTML5 Canvas. The animation involves drawing of stokes,images and moving images. Initially I took the easiest path for coding animation, which is clearing canvas every time and drawing new positions of objects. When tested this application on my laptop, in Chrome, everything worked fine and performance was satisfactory.
However when I ran the same application on my Xoom tablet, animation was extremely slow. Obviously clearing canvas and redrawing everything again and again was not a good idea. I did know that this approach was bad for animation, but since it worked fine on my laptop, I did not bother to change it initially. However I was surprised by how bad it performed on Android. I saw many complaints about performance of Canvas on Android in many forum posts and blogs. Apparently, as per many posts, performance of Canvas is much better on iOS than Android, but I can’t confirm this.
Most of the recommendations online for improving performance fell in two categories 1. Redraw only what has changed 2. Use multiple layers of canvas to cache objects that do not change during animation. The first option did not suit my application, because I had too many objects on the Canvas and I thought computing which objects need to be redrawn would have been only little better or as bad as redrawing every frame.
Fortunately my application does not have too many images moving at the same time. When an image changes position, I need to redraw what was hidden by the image in its previous location and draw the image at its new location. So I thought I would draw all objects, except the one that is about to be moved on a backup Canvas, and every time image moves, convert main canvas to an image and then draw this image every time image is moved before drawing moving image at a new location.One side effect of this approach is that image that is moving appear in the front even if there were other objects on top of it. But that is acceptable in my application.
So I started looking for Canvas API for converting it to an image. Initially I found two APIs for this –
- Canvas.toDataURL(imageType) : This function creates image (of given image type – this argument is optional) data as base64 encoded. This is useful if you want to save/send data in text format. But the size of data becomes too big. Also this function is not implemented in Android 2.2/2.3. It is implemented in Android 4.0 (ICS).
- Canvas.getImageData and Canvas.putImageData : You can specify top,left coordinates of canvas from where to get image data and width and height of image from that location. This returns ImageData object. You can use it in putImageData function to write the data back on the Canvas.
I did not want to use toDataURL because of base64 format (too big). get/put imageData looked promising. However for some reason it did not work as expected when used to get/put only part of the image. I still need to debug this issue.
Then I read this description of Canvas.drawImage() – you can use this function on “HTMLImageElement, an HTMLCanvasElement, or an HTMLVideoElement”. So you can use one Canvas as the first argument to this function and copy its content to another Canvas. This was something I was looking for. So I changed my animation code 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.
- When the image is moved, copy static content from the backup Canvas to the main Canvas at previous image location. For example, if the image that is moving, has width w1 and height h1 and it is being moved from x1, y1 to x2, y2 then I copy image from the backup Canvas from x1, y1, width w1 and height h1to the main Canvas at x1,y1. This will expose previously hidden object under the image that is being moved. Then I draw the image on the main Canvas at x2,y2
After above changes the performance is much better on Android. It is still not as good as I would have liked, but there is significant performance improvement.