Reputation: 163
I'm afraid I'm having a lot of difficulty in understanding the canvas.getContext("2d").save() end restore() functions in javascript.
I hope this example explains the problem. What should happen is that the canvas context is saved upon creation and when you change between the different tools the saved state should get restored, in order to reset the context each time.
However, if you use the pencil, then select the eraser, then go back to the pencil, what happens is the context.globalCompositeOperation continues to be "destination-out" : essentially the pencil continues to erase.
It would appear that any context parameters you don't hard-set don't get changed, ie. the saved context isn't being restored.
Would any of you kind people out there please help me understand where I'm going wrong?
document.querySelector("img").addEventListener("click",function(event) {
/* create a new canvas element in order to edit the image */
var canvas = document.createElement("canvas");
canvas.width = event.target.offsetWidth;
canvas.height = event.target.offsetHeight;
canvas.innerHTML = event.target.alt;
canvas.getContext("2d").drawImage(event.target,0,0);
/* here is where we save the context state */
canvas.getContext("2d").save();
/* replace the img element with canvas */
event.target.parentNode.replaceChild(canvas,event.target);
});
/*\
\*/
document.querySelector("aside").addEventListener("change",function(event) {
var canvas = document.querySelector("canvas");
if (!canvas) {
return;
}
var context = canvas.getContext("2d");
/* restore canvas state to reset context */
context.restore();
switch (event.target.value) {
case "pencil" :
context.lineJoin = "round";
context.lineCap = "round";
context.lineWidth = "6";
context.strokeStyle = "rgb(0,0,0)";
break;
case "eraser" :
context.globalCompositeOperation = "destination-out";
context.lineJoin = "round";
context.lineCap = "round";
context.lineWidth = "20";
break;
}
canvas.onmousedown = function(event) {
context.beginPath();
context.moveTo(event.offsetX,event.offsetY);
canvas.onmousemove = function(event) {
context.lineTo(event.offsetX,event.offsetY);
context.stroke();
}
document.querySelector("main").onmouseup = function(event) {
canvas.onmousemove = null;
this.onmouseup = null;
}
}
});
body {
display:flex;
}
main {
margin:0.5em;
}
main img,
main canvas {
display:inline-block;
width:200px;
height:200px;
text-align:center;
color:rgb(200,200,200);
background-color:rgb(250,250,250);
cursor:pointer;
}
aside {
margin:0.5em;
}
aside input[type='radio'] {
display:none;
}
aside input[type='radio'] + label {
display:block;
margin:0 auto;
box-sizing:border-box;
line-height:1.5em;
text-align:center;
padding:0 0.5em;
border-style:solid;
border-width:1px;
border-color:rgb(127,127,127);
border-radius:0.25em;
background:rgb(245,245,245);
cursor:pointer;
}
aside input[type='radio']:checked + label {
background:rgb(240,240,240);
}
<body>
<main>
<img width="200" height="200" alt="click here to start">
</main>
<aside>
<input id="canvas_context_pencil" name="canvas_context" type="radio" value="pencil">
<label for="canvas_context_pencil">pencil</label>
<input id="canvas_context_eraser" name="canvas_context" type="radio" value="eraser">
<label for="canvas_context_eraser">eraser</label>
<ol>
<li>click on image to create a canvas element</li>
<li>select either pencil or eraser to draw</li>
</ol>
</aside>
</body>
Upvotes: 0
Views: 717
Reputation: 136638
Internally, save
and restore
share a common stack of states, that you could see as an Array.
Every time you call save
, all the current default properties of the context are pushed in this stack.
Every time you call restore
, the last state is popped out of the stack, and all its saved properties are set to the context.
This means that each saved state, can be restored only once.
In your code, you do call save
only once, at init. Then you restore this state at first input's change
event, removing this initial state in the same time. All your subsequent calls to restore
will end being useless, because there is nothing in the saved states stack anymore.
Generally, I would advice to not use save and restore in such case, but instead set and reset every properties every time you need it; but since the question is about these methods, here is one way to make your code works:
Restore in each change event and save just after. This way, you will always start with the default properties.
document.querySelector("img").addEventListener("click", function(event) {
/* create a new canvas element in order to edit the image */
var canvas = document.createElement("canvas");
canvas.width = event.target.offsetWidth;
canvas.height = event.target.offsetHeight;
canvas.innerHTML = event.target.alt;
canvas.getContext("2d").drawImage(event.target, 0, 0);
/* replace the img element with canvas */
event.target.parentNode.replaceChild(canvas, event.target);
});
document.querySelector("aside").addEventListener("change", function(event) {
var canvas = document.querySelector("canvas");
if (!canvas) {
return;
}
var context = canvas.getContext("2d");
/* restore canvas state to reset context */
context.restore();
/* save now that it is default */
context.save();
switch (event.target.value) {
case "pencil":
context.lineJoin = "round";
context.lineCap = "round";
context.lineWidth = "6";
context.strokeStyle = "rgb(0,0,0)";
break;
case "eraser":
context.globalCompositeOperation = "destination-out";
context.lineJoin = "round";
context.lineCap = "round";
context.lineWidth = "20";
break;
}
canvas.onmousedown = function(event) {
context.beginPath();
context.moveTo(event.offsetX, event.offsetY);
canvas.onmousemove = function(event) {
// note that since you don't clear the canvas,
// you are actually drawing a lot of pixels over themselves,
// creating huge antialiasing artifacts.
context.lineTo(event.offsetX, event.offsetY);
context.stroke();
}
document.querySelector("main").onmouseup = function(event) {
canvas.onmousemove = null;
this.onmouseup = null;
}
}
});
body {
display:flex;
}
main {
margin:0.5em;
}
main img,
main canvas {
display:inline-block;
width:200px;
height:200px;
text-align:center;
color:rgb(200,200,200);
background-color:rgb(250,250,250);
cursor:pointer;
}
aside {
margin:0.5em;
}
aside input[type='radio'] {
display:none;
}
aside input[type='radio'] + label {
display:block;
margin:0 auto;
box-sizing:border-box;
line-height:1.5em;
text-align:center;
padding:0 0.5em;
border-style:solid;
border-width:1px;
border-color:rgb(127,127,127);
border-radius:0.25em;
background:rgb(245,245,245);
cursor:pointer;
}
aside input[type='radio']:checked + label {
background:rgb(240,240,240);
}
<body>
<main>
<img width="200" height="200" alt="click here to start" src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg">
</main>
<aside>
<input id="canvas_context_pencil" name="canvas_context" type="radio" value="pencil">
<label for="canvas_context_pencil">pencil</label>
<input id="canvas_context_eraser" name="canvas_context" type="radio" value="eraser">
<label for="canvas_context_eraser">eraser</label>
<ol>
<li>click on image to create a canvas element</li>
<li>select either pencil or eraser to draw</li>
</ol>
</aside>
</body>
Also note that there are a lot of things that should be refactored in your code, but it's not the point of this question...
Upvotes: 1