How to implement HTML5 Canvas undo function

HTML 5 is awesome. My most favorite feature is the canvas. It has many uses and of them is to allow the users of your website to draw free shapes.

If that’s what your website utilizes the canvas element for, by no doubt you have already identified the need for an ‘undo’ function. Perhaps you have already tried the .save() and .restore() functions, only to realize that they only save and restore the fill style of the drawing shapes.

This is exactly the problem I had, while creating my online Rage Comic Editor. Fortunately, I implemented a solution, which I would like to share with you.

The following tutorial assumes that you are familiar with the canvas element and its usage. It will only explain the technique I have discovered to save and restore a canvas drawing state. The implementation is (naturally) in JavaScript.

First, and most important, I have an online demo illustrating the tutorial, here. If you are inpatient or want to get straight to the code, feel free to navigate there and ‘view source’. It is well commented and should be self-explanatory.

In the demo I didn’t bother to implement a ‘free hand drawing’ tool. Instead, I have two buttons only – one of them draw lines on the canvas and the other button undoes one step at a time.

Now to the point… The whole technique consists of three parts:

  1. You need an array, to store the ‘restore points’ (much like Windows Restore Points). We will push states in it, when performing changes on the canvas, and will pop (and restore) states, out of it, when we press the ‘undo’ button. Here is how I have declared it: var restorePoints = [];
  2. As mentioned, before performing any change on the canvas, we need to store its state. We do this, by calling the toDataURL of the canvas, which exports the current drawing in your canvas, to a (PNG) image, encoded as base64. Basically, we ask the canvas to give us a text representation of the image that is drawn. Now we only need to insert this text, as an element of our restorePoints array. We do this using the restorePoints.push(…) command. The implementation of this step is in the saveRestorePoint() function in the example. Again – you have to call this method, before performing a change on the canvas!
  3. Now, how do we restore the canvas to its previous state, when the user clicks on an ‘undo’ button? We call a javascript method in which we have to create a new Image object in the memory, set its ‘src’ property to the last element in the restorePoints array (we do this using the array pop() method, which also removes the element from the array – exactly what we need!) and draw this image on the canvas (using the drawImage() function). So effectively what we do is: we take the last restoration point (image) and overlay it on top of the current state of the canvas. This will cause the effect of removing only the last change on your canvas, because we have restored to a point, which is previous to that change. We also need to remove the restoration point from the array, because we are currently ‘in it’ anyway.

And that’s about it. To summarize: maintain a javascript array of the previous states of the canvas. Before you perform a change on the canvas, save the current state, by exporting the canvas to a base64 encoded image. Store that state in the array of states, then do your change. When you want to ‘undo’, .pop() the last state from the canvas and draw it on top of everything. This will overwrite the whole canvas, but will only remove your last change, because the restoration point is before it.

I think there is no better explanation than a code sample, so go ahead and view the source of the demo. You can download the complete solution from here.

Cheers and Merry Christmas!

Advertisements

9 Responses to How to implement HTML5 Canvas undo function

  1. Is there some specific reason for using the base64 trick instead of just storing imageData [1] accessible via Canvas API?

    Note that in case you want to conserve some memory you are better off storing actual operations instead of image data. This brings some complications (how to replay till previous state ie.) but may be well worth it in some cases. Hybrid approaches (store images and ops) are possible as well.

    That said I think the approach you presented works more than well enough for most cases (low level of undo steps). You can easily beef it up a bit by storing step bounding boxes (no need to store whole image 🙂 ). This can be taken even further (store more, smaller bboxes).

    [1] http://dev.w3.org/html5/canvas-api/canvas-2d-api.html#imagedata

  2. hyankov says:

    Hi,

    I guess the mechanism I have described would work with imagedata as well. As a matter of facts, imagedata may not require that the background of the canvas is non-transparent. I have to check that.

    One benefit of the base64 encoded image, though, is that you can use to show thumbnails of the restoration points, much like the ‘history’ panel in Photoshop and similar.

    You are also right about the bounding boxes optimizations that can be done.

    But regarding the stored operations – I think this only works when your application allows drawing of free shapes (circles, rectangles, lines, free hand, etc), but not when you utilize ‘drawImage’ to ‘burn’ an image on the canvas 🙂

    Nice suggestions and a comment, thanks again!

  3. Good point about thumbnails! I’ll definitely keep that trick in mind. 🙂

    Considering stored ops you are right in saying that it works better when you are dealing with free shapes. The savings are definitely more tangible in that case. I cannot see why ‘drawImage’ based ops wouldn’t work in this system as well, though. I see it as an op like any other.

    Note that the idea is to store actual ops used to draw the image (ie. lineTo, whatnot). I handle this using a specific wrapper that records drawn stroke. This recorded data can then be pushed to the undo stack and later on replayed. Having your own API also makes it easier to optimize various bits and implement some custom ops that aren’t available in the actual API.

    Mmm… I might have to write a blog post or something about this subject to expand on the idea a bit more. 🙂

    • hyankov says:

      The way I understand ‘stored ops’ approach is the following: You store a list of the performed operations on the canvas. When you do ‘undo’, you clear up the whole canvas, and perform each one of the stored operations. Basically, redraw the drawing, by repeating each of the already performed steps. This will work fine for lines, rectangles and etc. What about when you drawImage(…) on the canvas? The original img object you used as a source for this operation may no longer exist. I guess if you really want to do it that way, you have to first convert the img src to base64, so you can safely put it in the stack, without worrying that it will disappear later.

      Obviously this article is not the best ever implementation, as you clearly pointed out many points that can use optimization. But it is a very quick start for someone who just want to get it working with a few lines of code 🙂

      • True. Just storing reference to the element won’t be enough as its image data will obviously get changed since the op. As you mentioned just doing some extra conversion ie. would do the trick in this case. 🙂

        One of the cool things about implementing an API of your own for recording is that you can make it work using relative coordinates instead of pure pixel based ones. This way you effectively decouple the resolution of the canvas from your recorded data. The main advantage of this is that later on you may render a higher resolution version of your recording. Really handy for a drawing app. 🙂

        I agree that the “naive” approach is more that fine for simple cases. There’s no denying that. I almost wish the Canvas API had something like this built-in…

  4. Fredrik says:

    Hey Hristo, thanks for sharing the undo code. I like the Rage Comic Editor, that’s way cool!

    I’m just curious how you implemented the images and especially the resizing of them. Especially since they can be dragged outside the visible canvas. Are you keeping them as floating keeping track of x,y and copying them in at export time? or is it a separate larger canvas and then copying between canvases. I’m contemplating the div solution myself now assuming it has some more options for styling the resizing and some JS libraries has some easy code for that.

    Cheers,
    Fredrik

    • hyankov says:

      Hi Fredrik,

      Resizing is done using the standard jQuery .resizable – http://jqueryui.com/demos/resizable/. Yes, images can be dragged anywhere on the screen, they are not limited within any boundaries. Since each one of them has an absolute position on the screen, they track their X and Y coordinates ‘by themselves’. Then at export time, I just ‘burn’ the images on the canvas, putting them with their correct position on the canvas. Those positions are determined by subtracting the canvas top X,Y from the image absolute X,Y.

      Send me your email at hristo.yankov@gmail.com

  5. Sharmila says:

    I have used the same code in my programme but I am unable to draw anything on canvas

  6. rahul says:

    hi, i need a canvas which allows to draw rectangle, line, sketch & should support for undo & redo operations. If anyone know please share the source code to rahulshetty700@gmail.com.
    thanks in advance

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: