Development of undo and redo functionality with canvas

canvas html5 javascript mootools webmaster

Sometimes I like playing with html5 and javascript. Today I was playing around with a sort of paint project for mootools. Clearly I used the html5 canvas element and I wrote some tools (pencil, rectangle, ...) to draw in it.

Well, one of the first problems encountered is the need for a undo/redo functionality. Let's see a way to implement such thing.

Essentially when drawing we can imagine we pass through a series of different states. Each state represents the canvas aspect at that time. Clearly we have a continuous set of states, but it would be to "expensive" to consider and implement this way.

So we may consider a discrete set of states, where a new state is created consequentially to a user action (and not time dependent). Let's consider the creation of a new state consequent to a mousedown-mouseup action of the user, in other words a new state is created when the user draws something in one move.

Now imagine to consider three categories: past, present and future.

The present category includes only the present state, the canvas state. The past category includes all the previous states (undo). The future category contains all the states "after the present" (redo).

The present category is represented by the present canvas state itself. The other two categories need an array to store all the states.

So essentially we need two array to store all the undo (1, 2, 3) and redo (5, 6) states.
The first element of the UNDO array is the state 1, the last is the state 3. The first element of the array REDO is the state 6, the last the state 5! Such organization allows to use always the pop method when retrieving both array elements.

When should we store a new state?

Well, we should store a state every time before doing any change to the canvas, and in this case we store it in the UNDO array. But also when performing an undo/redo operation we have to store the present state.

What happens when performing an undo action?

For simplicity I refer to the previous figure. It happens that we want the present state to be the number 3, but what about the state number 4? Clearly it must shift to the end of the REDO array (in the future), so that it would be the first to be restored when performing a redo action. So after an undo action the new situation will be
UNDO (1, 2)
REDO (4, 5, 6)
described by a right side shift.

When performing a redo action it's all the same but in the opposite verse.

The code

Store a state before doing any changes

My pencil tool class has a start method, called when the user press the mouse button, it's something like:

start: function(evt) {
  this.draw = true;
  var x,y; x = evt.page.x - this.canvas.getCoordinates().left;
  y = evt.page.y - this.canvas.getCoordinates().top; 
  this.ctx.fillRect(x-(this.dim/2).round(), y-(this.dim/2).round(), this.dim, this.dim);
  this.ctx.beginPath(); this.ctx.moveTo(x, y);

What is important here is the 2nd line, where the present state is stored by the wrapper object (the one who controls all the tools) before drawing the canvas.
Now let's see the saveState method:

saveState: function(history) {
  if(history) history.push(this.canvas.toDataURL("image/jpeg"));
  else this.undo_history.push(this.canvas.toDataURL("image/jpeg"));

If a specific array (history) is passed then it stores the actual canvas image in it, otherwise it stores it in the UNDO array (I named it undo_history). The image is stored as a base_64 encoded string through the method toDataURL, see the canvas API for more about it.

Implement the redo/undo action

Both the undo and redo actions are implemented through this method, called with opposite parameters:

restoreState: function(pop, push) {
  var restore_state = pop.pop();
  var img = new Element('img', {'src':restore_state});
  img.onload = function() {
    this.canvas.ctx.drawImage(img, 0, 0, 600, 400, 0, 0, 600, 400);  

This method expects two parameters, the array from which get the state to restore (pop), and the one to which store the present state (push). For example if we perform an undo action we would call this.restoreState(UNDO, REDO).

Then first of all the present state is stored in the "push" array, then the desired state (past or future) is taken from the "pop" array, and a new image is created with its content (a base_64 encoded string). Then when the image is ready, it's inserted in the canvas (drawImage method), and so the desired state overwrites the present one. That's all. Now if we perform the opposite action, all shifts in the other direction and we get the starting situation.


Check out the codepen demo here.

Subscribe to abidibo.net!

If you want to stay up to date with new contents published on this blog, then just enter your email address, and you will receive blog updates! You can set you preferences and decide to receive emails only when articles are posted regarding a precise topic.

I promise, you'll never receive spam or advertising of any kind from this subscription, just content updates.

Subscribe to this blog

Comments are welcome!

blog comments powered by Disqus

Your Smartwatch Loves Tasker!

Your Smartwatch Loves Tasker!

Now available for purchase!