Say I have a simple canvas element, and draw a plicated, 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 putationally 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>
Say I have a simple canvas element, and draw a plicated, 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 putationally 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>
Share
Improve this question
asked Oct 13, 2016 at 17:50
James PatersonJames Paterson
2,9253 gold badges32 silver badges43 bronze badges
1
- You would need to keep track of each state between as everything is merged to pixels (in place) on the canvas. Create a simple undo-redo stack (or try mine here: github./epistemex/undo-redo). – user1693593 Commented Oct 13, 2016 at 23:53
2 Answers
Reset to default 7You 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.
You can create an <img>
element, call canvas.toDataURL()
to store original canvas
at saveState(), use
context.clearRect()to clear
canvas,
context.drawImage()to restore saved
canvas`
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 putationally 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>