Making a 'History Panel' for a paint program - javascript

I'm making a very simple 'pixel-painting' program using HTML5/Canvas.
I'd like to give the user an option to go back in 'history', like the History panel in Photoshop / Adobe programs.
Basically it would be an undo button, but you'd be able to go back to the start of your actions, and there would also be a log showing the details of each action.
Is this possible? How would I even start storing this data?
How much memory is available within the Chrome browser in order to allow this on one page? – (Sorry if that is silly to ask, still quite new to Javascript and working within the browser.)
I have read this undo button Question, which is similar but I'd like to make the info about data being stored visible.
Thank you so so much for any help you can give!

You would need to build a simple undo-redo stack. Then you need to decide if you will store vector data or image data. The latter is more efficient but can also take up much more memory. You may have cases where you want to store both types of data (path on top of images).
The method would be in simple steps:
Store initial state for the new document. Keep a stack pointer pointing to the next free slot (for example using an array).
When mouse down is hit (or some other operation that will cause a change is started) move stack pointer forward.
When mouse button is released, make a snapshot, create thumbnail etc. It's up to you if you want to store the drawing as points or as a bitmap. If bitmap data you can get around storage space by compressing it using for example zip. Move stack pointer forward. If there exists snapshots in the stack at this point remove them.
When you need to undo, simply draw back previous stored step and move the stack pointer back. By keeping the snapshots you can do redo by moving stack pointer forward and redraw the snapshot, if any.
And finally, to visualize the undo-redo stack you can simply render each snapshot to a separate canvas at scale and extract that as an image which you put in the list.
Note: when creating a undo state it's important to clear any snapshots after the new stack pointer position. This is because if undo has been used, redo can be used if no changes. However, if undo was used and new drawing was added this would invalidate the next states so they have to be removed.
As to browser memory it will depend on the user's system. Some have a few gigabytes, other has a lot. There is no way to know. You would have to chose a UX strategy suitable for your scenario as well as target audience.
Example
This does not implement the logistics for handling the sync of the thumbnails, but has most other parts. I'll leave the rest as an exercise.
var ctx = c.getContext("2d"),
stack = [], // undo-redo stack
sp = 0, // stack pointer
isDown = false; // for drawing (demo)
capture(); // create an initial undo capture (blank)
ctx.lineCap = "round"; // setup line for demo
ctx.lineWidth = 4;
// simple draw mechanism
c.onmousedown = function(e) {
sp++; // on mouse down, move stack pointer to next slot
isDown = true; // NOTE: clear any snapshots after this point (not shown)
var pos = getXY(e); // start drawing some line - how you draw is up to you
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmousemove = function(e) {
if (!isDown) return; // only for drawing
var pos = getXY(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmouseup = function() {
if (!isDown) return;
isDown = false;
capture(); // capture an undo state
makeThumb(); // create and insert a thumbnail of state
};
function capture() {
stack[sp] = c.toDataURL(); // one way, you could use getImageData,
// or store points instead.. it's up to you
}
// Creates a thumbnail of current canvas and insert into visible undo stack
function makeThumb() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 64;
var ctxTmp = canvas.getContext("2d");
ctxTmp.drawImage(c, 0, 0, canvas.width, canvas.height);
undos.appendChild(canvas);
}
// UNDO button clicked
undo.onclick = function() {
var img = new Image; // restore previous state/snapshot
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer back and get previous snapshot
if (sp > 0) img.src = stack[--sp];
};
// REDO button clicked
redo.onclick = function() {
// anything we can redo?
if (sp < stack.length) {
var img = new Image;
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer forward and get next snapshot
img.src = stack[++sp];
}
};
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
#c {background:#ccc}
<button id=undo>Undo</button>
<button id=redo>Redo</button><br>
<canvas id=c width=500 height=500></canvas>
<div id=undos></div>

You could copy the current canvas to a separate one each time an action is performed. Simply displaying old canvases could serve as an action log.
You can send a canvas to drawImage directly:
destContext.drawImage( srcCanvas, 0, 0 );
If that approach consumes too much memory, an alternative is to store all the commands in a stack, remove the last element when undoing, and redraw everything from scratch.

Related

Android 4.2 Stock browser. Canvas clearRect() sometimes doesn't work

So I have a problem with clearRect in js-canvas-animation. This problem happens only on Android API 16, and only in the moment when animation is restart.
I'm using setInterval() for my animation (here is simplify code)
function start() {
clearInterval(animationInterval);
x = 0;
canvas = document.getElementById("animationCanvas");
ctx = canvas.getContext("2d");
animationInterval = setInterval(func, 30);
}
function func() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(halfWidth - x, 0, 250, 150);
x += extensionStep;
}
It is look like a shore. But every time, when i restart animation with help start function, under new animation i see last frame previous animation. Already i was trying beginPath(), save, stroke. I check all question on SO and nothing.
How i can clear the background under animation?
So after several hours, i found one of the solves. It is strange bug for old android, because new android (i checked on api 24) it fixed. For hard cleaning you can detach canvas from DOM and reattach again:
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.style.display = 'none';// Detach from DOM
canvas.offsetHeight; // Force the detach
canvas.style.display = 'inherit'; // Reattach to DOM
It is simple operation and is not resource intensive.

HTML5 Canvas: Setting context on resize

I have a project using HTML5 Canvas (createjs) and I've had issues with spikes on text strokes, meaning I have to set the miterlimit of the 2d context. However, the parent (which I have no control over) scales the canvas when the window is resized, which obviously resets the canvas context.
Now, I want to avoid putting an onresize event inside the client - my first thought is just to use the createjs Ticker thus:
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
var ctx = canvas.getContext('2d');
ctx.miterLimit = 2;
}
Though this seems a little wasteful, is there a more efficient way of doing this, without using DOM events?
Your approach might work, but its definitely a hack, since you can't expect that context properties will be maintained, or that they won't be applied in the wrong place.
If you do want to patch the display list to update the context, you can use the "drawstart" event, which fires before the display list is drawn:
stage.on("drawstart", function(e) {
var ctx = stage.canvas.getContext("2d");
ctx.miterLimit = 2;
});
However if you want a better approach that is instance-specific, you can extend the Text class to append any context properties you want. Here is a quick example where miterLimit is stored and applied any time the text is drawn. In this example, you can create multiple instances and set different miter limits on them. Note that you might want to also support other properties such as lineJoin.
http://jsfiddle.net/cr4hmgqp/2/
(function() {
"use strict"
function MiterText(text, font, color, miterLimit) {
this.Text_constructor(text,font,color);
this.miterLimit = miterLimit;
};
var p = createjs.extend(MiterText, createjs.Text);
p.draw = function(ctx, ignoreCache) {
ctx.miterLimit = this.miterLimit;
if (this.Text_draw(ctx, ignoreCache)) { return true; }
return true;
};
p.clone = function() {
return this._cloneProps(new MiterText(this.text, this.font, this.color, this.miterLimit));
};
createjs.MiterText = createjs.promote(MiterText, "Text");
}());
Note that this issue should hopefully be fixed in the next version of EaselJS. Here is the tracked issue: https://github.com/CreateJS/EaselJS/issues/781
Cheers.

html5 canvas memory leak

I am developing a camera based SPA. When I'm trying to resize the image it cause to a memory leak.
without executing resize
first executing resize
.
second executing resize
The memory never go down until I do refresh.
It is strange the same app http://shitu.baidu.com does release the memory.
Here is the my resize function.
var useRightPen = function (img, pre_img_wid, pre_img_hei, aft_img_wid, aft_img_hei, callback) {
var canvas = document.createElement('canvas');
var ctx=canvas.getContext('2d');
canvas.setAttribute("height", aft_img_hei);
canvas.setAttribute("width", aft_img_wid);
var orientation = 1;
if (!!(/http/.test(img.src))) {
//memory keeps the same before the drawimage
canvas.getContext('2d').drawImage(img, 0, 0, aft_img_wid, aft_img_hei);
//memory goes up after the draimage
callback(canvas.toDataURL('image/jpeg'), aft_img_wid, aft_img_hei);
}
canvas = null;
}
Appreciate for everyone.
---update--06-11--2015
I make one fildde http://jsfiddle.net/onTheRoad/dc17guba/
The demo will reset the image src after every pick.Memory goes high on its way.

How to stop a function from returning unless a key press is made

I'm attempting to create a simple program that sequentially displays a number of letters where each letter is presented alone (see the figure below). I'm also attempting to make the program work in such a way that the user needs to press a key on the keyboard before the next letter is presented.
I'm trying to do this by encapsulating each letter presentation within a single, almost self-contained function (singleTrial). My problem is is that I can't figure-out how to stop the singleTrial function from returning unless a key press is made (see the waitForResponse function). In this case, when the function returns, the next function call to SingleTrial is executed, making it so that only the second letter is displayed (because the first letter gets presented and then cleared in almost an instant). What am I doing wrong here? How can I make it so that the singleTrial function does not return unless a key press is made?
My ultimate goal is to make it so that I can iterate though a letter list, presenting each letter sequentially:
letterList.forEach(function(stim) {
singleTrial(stim);
});
Thanks in advance!
Here is my code:
var responseMade;
function onResponse() {
responseMade = true;
}
function singleTrial(stim, context, canvas) {
responseMade = false;
context.clearRect(0, 0, canvas.width, canvas.height);
window.removeEventListener('keydown', onResponse, false);
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillText(stim, 100, 10);
window.addEventListener('keydown', onResponse, false);
function waitForResponse() {
if (responseMade == false) {
setInterval(waitForResponse, 1000);
}
}
waitForResponse();
}
function main() {
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d');
singleTrial("A", context, canvas);
singleTrial("B", context, canvas);
}
main()
</script>
I can't figure-out how to stop the singleTrial function from returning unless a key press is made
You can't (currently; ES6's "generators" will offer a way to do it, but it still wouldn't be the best way here). Instead, what you do is hook up an event handler for when a key is pressed, and generate the next letter when that event occurs. This is a key concept in web (and other) programming: Rather than your program working in some set loop, you wait for and respond to events that occur.
For instance, remove waitForResponse from singleTrial, and change main:
function main() {
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
nextLetter = "A".charCodeAt(0);
document.addEventListener("keydown", function() {
singleTrial(String.fromCharCode(nextLetter), context, canvas);
++nextLetter;
}, false);
}
(You'll need to handle the addEventListener / attachEvent thing if you need to support old IE. From the canvas, I'm thinking you don't.)

Saving canvas image in phonegap after canvas is drawn

I am creating a image in a canvas and saving the image. I found a very nice plugin here.
Code for the image saving:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.src = imageURI;
imageObj.onload = function() {
contentW = $("#content").width();
canvas.width = 400;
canvas.height = 600;
context.drawImage(imageObj, 0, 0,canvas.width,canvas.height);
//the plugin
setTimeout(function(){
window.savephotoplugin(canvas,"image/png",device.version,function(val){
//returns you the saved path in val
alert("Photo Saved: " + val);
});
},3000)
}
The plugin works very nice only problem is that it is done before the canvas is even drawn. So I put a setTimeout to avoid it, however is there a way to detect when canvas is done and call the function after it. Tried jquery .change() didn't work.
If anyone finds this code useful feel free to use and the plugin is very nice :)
As you can see drawImage doesn't accept any callbacks. Also canvas doesn't define any events about its drawing process. So you choice of timeout is correct. Only one thing you could possibly improve. Use setTimeout(..., 0) instead of setTimeout(..., 3000). More details about this trick here

Categories

Resources