Paint canvas undo feature - javascript

These are the button functions:
$("#undo").click(function() {
Stack1.undo();
});
$("#redo").click(function() {
Stack1.redo();
});
This is the undo function:
function clearCanvas()
{
ctx.clearRect(0,0,canvasWidth,canvasHeight);
}
Stack1 = new Stack();
///////////////////
function Stack(firstImg , size) {
var drawStack = new Array();
var stackIndex = 0;
var stackTop = 0;
var stackFloor = 0;
var stackSize = size;
drawStack[0] = firstImg;
this.add = function() {
drawStack[++stackIndex%stackSize] = canvas.toDataURL("image/png");
if (stackIndex >= stackSize) stackFloor = (stackIndex +1) % stackSize ;
stackTop = stackIndex % stackSize;
}
this.undo = function () {
if (stackIndex%stackSize == stackFloor ) return;
clearCanvas();
var tmpImg = new Image();
tmpImg.src = drawStack[--stackIndex%stackSize];
ctx.drawImage(tmpImg, 0, 0);
}
this.redo = function () {
if (stackIndex%stackSize == stackTop) return;
clearCanvas();
var tmpImg = new Image();
tmpImg.src = drawStack[++stackIndex%stackSize];
ctx.drawImage(tmpImg, 0, 0);
}
}
Also I declare the array at the top:
var drawStack = [];
I also put this code before I draw each stroke in my mouse down method:
Stack1.add();
Here is my working example..draw 3 circles on screen then click undo, everything goes blank, then click it again and only 2 remain. Its close but I cannot figure out the last part.

You've made this more complicated than it needs to be. The pseudo code for how an undo function usually works goes:
currentState = 0
maxStates = 10
stateArray = []
initialize:
push the current state onto the top of stateArray
save:
if there are states in stateArray above the currentState
clear the states in stateArray above the current state
push the current state onto the top of stateArray
currentState++
if the size of stateArray exceeds maxStates
remove the oldest state from the bottom of stateArray
currentState--
undo:
if there are previous states in stateArray
currentState--
revert the canvas to stateArray[currentState]
redo:
if there are newer states in stateArray
currentState++
revert the canvas to stateArray[currentState]
As you can see:
You don't ever need the mod operator.
You don't need to keep track of the top and bottom of the stack, only the index of the current state, the number of states you want and the size of the stateArray.
You should clear the states in the stateArray above the current state when you add a new one for it to function the way people expect (unless you are going to implement a branching history which most applications do not).
Edit: I noticed a different problem in your code, you are trying to immediately draw the image to the canvas instead of waiting for it to load. At minimum your undo function should look like:
this.undo = function () {
if (stackIndex%stackSize == stackFloor) return;
var tmpImg = new Image();
tmpImg.src = drawStack[--stackIndex%stackSize];
tmpImg.onload = function() {
clearCanvas();
ctx.drawImage(this, 0, 0);
}
}
If your indexes are right, which I suspect they are not. You can look at an example implementation of the algorithm I described above in this fiddle.

Related

SVG Camera Pan - Translate keeps resetting to 0 evertime?

Well i have this SVG canvas element, i've got to the point so far that once a user clicks and drags the canvas is moved about and off-screen elements become on screen etc....
However i have this is issue in which when ever the user then goes and click and drags again then the translate co-ords reset to 0, which makes the canvas jump back to 0,0.
Here is the code that i've Got for those of you whio don't wanna use JS fiddle
Here is the JSfiddle demo - https://jsfiddle.net/2cu2jvbp/2/
edit: Got the solution - here is a JSfiddle DEMO https://jsfiddle.net/hsqnzh5w/
Any and all sugesstion will really help.
var states = '', stateOrigin;
var root = document.getElementById("svgCanvas");
var viewport = root.getElementById("viewport");
var storeCo =[];
function setAttributes(element, attribute)
{
for(var n in attribute) //rool through all attributes that have been created.
{
element.setAttributeNS(null, n, attribute[n]);
}
}
function setupEventHandlers() //self explanatory;
{
setAttributes(root, {
"onmousedown": "mouseDown(evt)", //do function
"onmouseup": "mouseUp(evt)",
"onmousemove": "mouseMove(evt)",
});
}
setupEventHandlers();
function setTranslate(element, x,y,scale) {
var m = "translate(" + x + "," + y+")"+ "scale"+"("+scale+")";
element.setAttribute("transform", m);
}
function getMousePoint(evt) { //this creates an SVG point object with the co-ords of where the mouse has been clicked.
var points = root.createSVGPoint();
points.x = evt.clientX;
points.Y = evt.clientY;
return points;
}
function mouseDown(evt)
{
var value;
if(evt.target == root || viewport)
{
states = "pan";
stateOrigin = getMousePoint(evt);
console.log(value);
}
}
function mouseMove(evt)
{
var pointsLive = getMousePoint(evt);
if(states == "pan")
{
setTranslate(viewport,pointsLive.x - stateOrigin.x, pointsLive.Y - stateOrigin.Y, 1.0); //is this re-intializing every turn?
storeCo[0] = pointsLive.x - stateOrigin.x
storeCo[1] = pointsLive.Y - stateOrigin.Y;
}
else if(states == "store")
{
setTranslate(viewport,storeCo[0],storeCo[1],1); // store the co-ords!!!
stateOrigin = pointsLive; //replaces the old stateOrigin with the new state
states = "stop";
}
}
function mouseUp(evt)
{
if(states == "pan")
{
states = "store";
if(states == "stop")
{
states ='';
}
}
}
In your mousedown function, you are not accounting for the fact that the element might already have a transform and you are just overwriting it.
You are going to need to either look for, and parse, any existing transform. Or an easier approach would be to keep a record of the old x and y offsets and when a new mousedown happens add them to the new offset.

Drawing application undo button

i have problem with undo button for my drawing application
<input id="undo" type="image" src="images/undo.ico" onclick="cUndo()" width="25" height="25">
var cPushArray = new Array();
var cStep = -1;
var ctx;
// ctx = document.getElementById('myCanvas').getContext("2d");
function cPush() {
cStep++;
if (cStep < cPushArray.length) { cPushArray.length = cStep; }
cPushArray.push(document.getElementById('myCanvas').toDataURL());
}
function cUndo() {
if (cStep > 0) {
cStep--;
var canvasPic = new Image();
canvasPic.src = cPushArray[cStep];
canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); }
}
}
But this doesn't work.Please help
First remark : As #markE underlines, saving with DataURL has a high memory cost. You might consider saving the draw commands + their arguments within an array instead.
Seek for tuts/Stack Overflow post on the topic, out of a few posts you should get some nice ideas.
Anyway, you can go with the dataURL solution in a first time to get your application working (with a limit of 20 undos or like to avoid memory explosion), then you can later improve the undo to reach a higher limit.
I updated my code to handle such a stack limit.
For your issue : onload should be hooked prior to setting the src, but anyway with a DataURL you are not async : the image is built at once, so no need to hook unload.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
var historic = [];
var maxHistoricLength = 20; // might be more or less, depending on canvas size...
function saveForUndo() {
historic.push(canvas.toDataURL());
if (historic.length === maxHistoricLength +1) historic.shift();
}
function canUndo() {
return (historic.length !== 0 );
}
function undo() {
if (!canUndo()) return;
var lastDataURL = historic.pop();
var tmpImage = new Image();
tmpImage.src = lastDataURL;
ctx.drawImage(tmpImage, 0, 0);
}

html5 canvas code blanking out

I am in desperate need to fix a problem I have in html5 canvas. I am making an educational game, but after adding another photo and making the necessary code changes, but the canvas blanked out. I reverted the changes, but the problem persisted. I spent the past week or so trying to fix it, but I've run out of ideas :( Here is the relevent code (if anyone needs another part, just say so):
// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 1041;
canvas.height = 550;
document.body.appendChild(canvas);
// Background image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function () {
bgReady = true;
};
bgImage.src = "images/introbackground.png";
//.... rest of the photos load like this so I won't put it...
// Level 1 background 1 image
var bbg1Ready = false;
var bbg1Image = new Image();
bbg1Image.onload = function () {
bbg1Ready = false; //Exception since it shouldn't load at the beginning
};
bbg1Image.src = "images/pollutedbeach1.png";
// Game objects
var hero = {
speed: 200 // movement in pixels per second
};
var level1;
var level2;
var biolevel = false;
//...Code to make the hero picture move with arrow keys and to reset the game are skipped...
if (hero.x <= (level1.x + 345)
&& level1.x <= (hero.x + 32)
&& hero.y <= (level1.y +50)
&& level1.y <= (hero.y + 32)){
biolevel = true;
return biolevel;
}
var render = function () {
if (bgReady) {
ctx.drawImage(bgImage, 0, 0);
}
//...The same for all photos previously loaded...
if (biolevel == true){
level1Ready = false;
level2Ready = false;
bbg1Ready = true;
hero.speed = 125;
}
};
//...Then the main game loop to animate it...
Sorry It's kind of long, but I really need a solution. Thanks!

Canvas game only starts drawing when the window is inactive

I noticed something weird in my canvas code: Im making a game with little flying ghosts. The class for the ghost is below. When I just draw 1 or 2 of em by manually adding the code and having em fly to the right by updating the x every frame for example everything runs smoothly and fine.
Now I ran another test and added a ghost every 100 frames to an array, update its x by 100 and then draw that ghost to the frame. (code is below the first block, the draw function).
Now the problem is that they are actually added and drawn, but I dont see em on the board until I make the window inactive by clicking on the taskbar for example.
Any1 got a clue what is going wrong here?
/*
* Class for little ghosts
*/
function Ghost (name) {
this.name = name;
this.ghost = new Image();
this.ghost.src = "img/ghost.png";
this.ghostWidth = 150;
this.ghostHeight = 100;
this.ghostSpriteOffsetX = 0;
this.ghostSpriteOffsetY = 0;
this.ghostX = 0;
this.ghostY = 0;
}
Ghost.prototype.drawGhost = function() {
context2D.drawImage(this.ghost, this.ghostSpriteOffsetX, this.ghostSpriteOffsetY, this.ghostWidth, this.ghostHeight, this.ghostX, this.ghostY, this.ghostWidth, this.ghostHeight);
};
Ghost.prototype.goToX = function(x) {
this.ghostX = x;
};
Ghost.prototype.goToY = function(y) {
this.ghostY = y;
};
Ghost.prototype.turnPink = function() {
this.ghostSpriteOffsetX = 0;
};
Ghost.prototype.turnBlue = function() {
this.ghostSpriteOffsetX = 150;
};
Ghost.prototype.turnPurple = function() {
this.ghostSpriteOffsetX = 300;
};
-
function draw()
{
// clear board
context2D.clearRect(0, 0, canvas.width, canvas.height);
if(frame%100==0){
ghosts[ghostId] = new Ghost("g-"+frame);
ghosts[ghostId].goToX(frame-100);
ghostId++;
}
// Draw ghost
for (i=0; i<ghosts.length; i++)
{
ghosts[i].drawGhost();
}
frame++;
}

How to add undo-functionality to HTML5 Canvas?

I have a Sketching app done in all HTML5 and Javascript, and I was wondering how I would create an Undo Button, so you could undo the last thing you drew. Any idea?
You have to store all modifications in a datastructure. Then you can delete the latest modification if the user wants to undo it. Then you repaint all drawing operations from your datastructure again.
On http://arthurclemens.github.io/Javascript-Undo-Manager/ I have a working example of undo with a canvas element. When you make a modification, you feed the undo manager the undo and redo methods. Tracking of the position in the undo stack is done automatically. Source code is at Github.
The other option, if you need to manipulate objects is to convert your canvas to SVG using a library that preserves the Canvas API preventing a rewrite.
At least one such library exists at this time (November 2011):
SVGKit
Once you have SVG, it is much easier to remove objects and much more without the need to redraw the entire canvas.
Here is a solution that works for me. I have tried it in the latest versions of Firefox and Chrome and it works really well in those two browsers.
var isFirefox = typeof InstallTrigger !== 'undefined';
var ctx = document.getElementById('myCanvas').getContext("2d");
var CanvasLogBook = function() {
this.index = 0;
this.logs = [];
this.logDrawing();
};
CanvasLogBook.prototype.sliceAndPush = function(imageObject) {
var array;
if (this.index == this.logs.length-1) {
this.logs.push(imageObject);
array = this.logs;
} else {
var tempArray = this.logs.slice(0, this.index+1);
tempArray.push(imageObject);
array = tempArray;
}
if (array.length > 1) {
this.index++;
}
return array;
};
CanvasLogBook.prototype.logDrawing = function() {
if (isFirefox) {
var image = new Image();
image.src = document.getElementById('myCanvas').toDataURL();
this.logs = this.sliceAndPush(image);
} else {
var imageData = document.getElementById('myCanvas').toDataURL();
this.logs = this.sliceAndPush(imageData);
}
};
CanvasLogBook.prototype.undo = function() {
ctx.clearRect(0, 0, $('#myCanvas').width(), $('#myCanvas').height());
if (this.index > 0) {
this.index--;
this.showLogAtIndex(this.index);
}
};
CanvasLogBook.prototype.redo = function() {
if (this.index < this.logs.length-1) {
ctx.clearRect(0, 0, $('#myCanvas').width(), $('#myCanvas').height());
this.index++;
this.showLogAtIndex(this.index);
}
};
CanvasLogBook.prototype.showLogAtIndex = function(index) {
ctx.clearRect(0, 0, $('#myCanvas').width(), $('#myCanvas').height());
if (isFirefox) {
var image = this.logs[index];
ctx.drawImage(image, 0, 0);
} else {
var image = new Image();
image.src = this.logs[index];
ctx.drawImage(image, 0, 0);
}
};
var canvasLogBook = new CanvasLogBook();
So every time you draw any thing you will there after run function canvasLogBook.logDrawing() to store a snapshot of the canvas and then you can call canvasLogBook.undo() to undo and canvasLogBook.redo() to redo.

Categories

Resources