I've created an HTML5 Drawing App that has basic functions that allow the user to select a color to draw with, change the size (radius) of the drawing tool, undo, redo, and completely clear the canvas.
I recently added an Eraser tool using the globalCompositionProperty (desitnation-out) to erase the selected areas of the canvas. This part works fine, but when I go to undo the erasing, the entire canvas is cleared out and the redo function doesn't work. When I resume drawing with the regular drawing tool (using source-over), the undo/redo works. I've included the code for a few of the functions used in the app. I store a snapshot of the canvas each time the drawing tool is disengaged. This is added to an array that is used to undo and restore the canvas.
Can someone explain what I could be doing wrong here? I'm not sure I understand how the globalCompositeOperation property affects the canvas DataURI that's saved.
function storeSnapshot() {
cStep++;
if (cStep < cPushArray.length) { cPushArray.length = cStep; }
cPushArray.push(canvas.toDataURL());
}
var putPoint = function(e){
if(dragging){
context.lineTo(e.clientX - 174, e.clientY - 50);
context.stroke();
context.beginPath();
if(eraser == true){
context.globalCompositeOperation="destination-out";
}else{
context.globalCompositeOperation="source-over";
}
context.arc(e.clientX - 174, e.clientY - 50 , radius, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.moveTo(e.clientX - 174, e.clientY - 50);
}
}
UPDATE: After playing around with this for a while I tried something that fixes my issue. I'm not exactly sure if it's the right way to do things, but I figured I'd show you all what I did.
I have a disengage function that is triggered on mouse up. Here's the function before:
var disengage = function(){
dragging = false;
context.beginPath();
storeSnapshot();
}
I added the bit of code and this allows the user to use the eraser tool and undo/redo the action. I'm not exactly sure why this works yet, so if anyone has insight, I'd greatly appreciate it.
var disengage = function(){
dragging = false;
context.beginPath();
if(eraser == true)
{
context.globalCompositeOperation="source-over";
}
storeSnapshot();
}
I was having the exact same issue as you, and I'm so glad I came across your question/solution because it was a lightbulb moment for me. As soon as I saw your solution, it made complete sense what was going on.
To provide insight into why your solution works, globalCompositeOperation
is a property of the canvas that basically dictates how stuff, in this case a line stroke, is put on the canvas. By default it is source-over, which just places a new line stroke on top of any existing strokes. As you mentioned, erasing uses destination-out, which keeps all existing content where it DOESN'T overlap the new line stroke (hence the erasing). For some informative diagrams and explanations I would highly recommend the MDN docs for globalCompositeOperation
: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
So if you go into erase mode (for you, eraser = true), erase, and then immediately try to undo, the globalCompositeOperation will still be set to destination-out. This means that when your undo attempts to draw the stored image to the canvas, everything will disappear because it is essentially still erasing (as destination-out says, it will chuck any of the existing content where it overlaps with new content, which in this case is the entire canvas space). Therefore, you need to set it back to source-over like you do in your solution so that the restored image actually appears on the canvas. Hopefully that helps!