James Paterson
James Paterson

Reputation: 2890

Reset canvas to previous picture after modifications

Say I have a simple canvas element, and draw a complicated, resource intensive picture on it. I then draw some simple lines on the picture. Would there be a way to "save" the state of the canvas (before the lines are drawn), and then redraw the state to erase any further changes made. I did try this with save() and restore() but I don't think the state for that includes the current shapes on the canvas. See my demo below.

var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');

function init() {
  // This is some computationally intensive drawing we don't want to repeat
  context.fillStyle = "rgb(150,29,28)";
  context.fillRect(40, 40, 255, 200);
  context.fillStyle = "rgb(150,83,28)";
  context.fillRect(10, 10, 50, 50);
  context.fillStyle = "rgb(17,90,90)";
  context.fillRect(5, 100, 200, 120);
  context.fillStyle = "rgb(22,120,22)";
  context.fillRect(200, 200, 90, 90);
  // Now we save the state so we can return to it
  saveState();
}

function lines() {
  // This is some drawing we will do and then want to get rid of
  context.beginPath();
  context.moveTo(125, 125);
  context.lineTo(150, 45);
  context.lineTo(200, 200);
  context.closePath();
  context.stroke();
}

function saveState() {
  //copy the data into some variable
}

function loadState() {
  //load the data from the variable and apply to canvas
}

init();
#canvas {
  border: 1px solid #000;
}
<canvas id="canvas" width="300" height="300"></canvas>
<button onClick="lines()">Draw over image</button>
<button onClick="loadState()">Restore</button>

Upvotes: 1

Views: 2923

Answers (2)

Blindman67
Blindman67

Reputation: 54026

You can easily just copy the canvas to a new one.

// canvas is the canvas you want to copy.
var canvasBack = document.createElement("canvas");
canvasBack.width = canvas.width;
canvasBack.height = canvas.height;
canvasBack.ctx = canvasBack.getContext("2d");
canvasBack.ctx.drawImage(canvas,0,0);

You treat the new canvas as if it is another image and can copy it to the original with

ctx.drawImage(canvasBack,0,0);

Rendering an image is done in hardware so can be done easily in realtime many times per frame. Because of this you can treat the canvases as layers (like photoshop) and using globalCompositeOperation create a wide range of adjustable FX.

You can convert to a dataURL but that is a much slower process and not quick enough for realtime rendering. Also keeping a copy of the DataURL string and then decoding it to an image will place a larger strain on memory than just creating a canvas copy (base64 encodes 3 bytes (24bit) in every 4 characters. As JS characters are 16 bits long storing data in base64 is very inefficient (64bits of memory used to store 24bits)

The an alternative is to store the canvas as a typed array with ctx.getImageData but this is also very slow, and can not handle realtime needs.

Upvotes: 7

guest271314
guest271314

Reputation: 1

You can create an <img> element, call canvas.toDataURL() to store original canvas at saveState(), usecontext.clearRect()to clearcanvas,context.drawImage()to restore savedcanvas`

var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
var _canvas;
var img = new Image;
img.width = canvas.width;
img.height = canvas.height;

function init() {
  // This is some computationally intensive drawing we don't want to repeat
  context.fillStyle = "rgb(150,29,28)";
  context.fillRect(40, 40, 255, 200);
  context.fillStyle = "rgb(150,83,28)";
  context.fillRect(10, 10, 50, 50);
  context.fillStyle = "rgb(17,90,90)";
  context.fillRect(5, 100, 200, 120);
  context.fillStyle = "rgb(22,120,22)";
  context.fillRect(200, 200, 90, 90);
  // Now we save the state so we can return to it
  saveState(canvas);
}

function lines() {
  // This is some drawing we will do and then want to get rid of
  context.beginPath();
  context.moveTo(125, 125);
  context.lineTo(150, 45);
  context.lineTo(200, 200);
  context.closePath();
  context.stroke();
}

function saveState(c) {
  _canvas = c.toDataURL();
  //copy the data into some variable
}

function loadState() {
  //load the data from the variable and apply to canvas
  context.clearRect(0, 0, canvas.width, canvas.height);
  img.onload = function() {
    context.drawImage(img, 0, 0);
  }
  img.src = _canvas;
}


init();
#canvas {
  border: 1px solid #000;
}
<canvas id="canvas" width="300" height="300"></canvas>
<button onClick="lines()">Draw over image</button>
<button onClick="loadState()">Restore</button>

Upvotes: 0

Related Questions