I am making a paint app in HTML using JavaScript and Flask-Python. Currently, I am able to draw lots of pencil drawings and shapes like rectangles/circles without any problem. The following functionality that I am trying to implement for this application is a undo function.
I store the strokes and the canvas drawing data in a JS object in the following manner:
canvas_data = { "pencil": [], "line": [], "rectangle": [], "circle": [], "eraser": [], "last_action": -1 };
All of the key names should be self-explanatory except last_action
. I use this last_action
variable to know the last category used by the user so that I can later use this information to implement the undo function.
var canvas = document.getElementById("paint");
var ctx = canvas.getContext("2d");
var pi2 = Math.PI * 2;
var resizerRadius = 8;
var rr = resizerRadius * resizerRadius;
var width = canvas.width;
var height = canvas.height;
var curX, curY, prevX, prevY;
var hold = false;
ctx.lineWidth = 2;
var fill_value = true;
var stroke_value = false;
var canvas_data = {
"pencil": [],
"line": [],
"rectangle": [],
"circle": [],
"eraser": [],
"last_action": -1
};
// //connect to postgres client
// var pg = require('pg');
// var conString = "postgres://postgres:database1@localhost:5432/sketch2photo";
// client = new pg.Client(conString);
function color(color_value) {
ctx.strokeStyle = color_value;
ctx.fillStyle = color_value;
}
function add_pixel() {
ctx.lineWidth = 1;
}
function reduce_pixel() {
if (ctx.lineWidth == 1) {
ctx.lineWidth = 1;
} else {
ctx.lineWidth -= 1;
}
}
function fill() {
fill_value = true;
stroke_value = false;
}
function outline() {
fill_value = false;
stroke_value = true;
}
function reset() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas_data = {
"pencil": [],
"line": [],
"rectangle": [],
"circle": [],
"eraser": [],
"last_action": -1
};
}
// pencil tool
function pencil(data, targetX, targetY, targetWidth, targetHeight) {
canvas.onmousedown = function(e) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
hold = true;
prevX = curX;
prevY = curY;
ctx.beginPath();
ctx.moveTo(prevX, prevY);
};
canvas.onmousemove = function(e) {
if (hold) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
draw();
}
};
canvas.onmouseup = function(e) {
hold = false;
};
canvas.onmouseout = function(e) {
hold = false;
};
function draw() {
ctx.lineTo(curX, curY);
ctx.stroke();
canvas_data.pencil.push({
"startx": prevX,
"starty": prevY,
"endx": curX,
"endy": curY,
"thick": ctx.lineWidth,
"color": ctx.strokeStyle
});
canvas_data.last_action = 0;
}
}
// line tool
function line() {
canvas.onmousedown = function(e) {
img = ctx.getImageData(0, 0, width, height);
prevX = e.clientX - canvas.offsetLeft;
prevY = e.clientY - canvas.offsetTop;
hold = true;
};
canvas.onmousemove = function linemove(e) {
if (hold) {
ctx.putImageData(img, 0, 0);
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(curX, curY);
ctx.stroke();
canvas_data.line.push({
"startx": prevX,
"starty": prevY,
"endx": curX,
"endY": curY,
"thick": ctx.lineWidth,
"color": ctx.strokeStyle
});
ctx.closePath();
canvas_data.last_action = 1;
}
};
canvas.onmouseup = function(e) {
hold = false;
};
canvas.onmouseout = function(e) {
hold = false;
};
}
// rectangle tool
function rectangle() {
canvas.onmousedown = function(e) {
img = ctx.getImageData(0, 0, width, height);
prevX = e.clientX - canvas.offsetLeft;
prevY = e.clientY - canvas.offsetTop;
hold = true;
};
canvas.onmousemove = function(e) {
if (hold) {
ctx.putImageData(img, 0, 0);
curX = e.clientX - canvas.offsetLeft - prevX;
curY = e.clientY - canvas.offsetTop - prevY;
ctx.strokeRect(prevX, prevY, curX, curY);
if (fill_value) {
ctx.fillRect(prevX, prevY, curX, curY);
}
canvas_data.rectangle.push({
"startx": prevX,
"starty": prevY,
"width": curX,
"height": curY,
"thick": ctx.lineWidth,
"stroke": stroke_value,
"stroke_color": ctx.strokeStyle,
"fill": fill_value,
"fill_color": ctx.fillStyle
});
canvas_data.last_action = 2;
}
};
canvas.onmouseup = function(e) {
hold = false;
};
canvas.onmouseout = function(e) {
hold = false;
};
}
// circle tool
function circle() {
canvas.onmousedown = function(e) {
img = ctx.getImageData(0, 0, width, height);
prevX = e.clientX - canvas.offsetLeft;
prevY = e.clientY - canvas.offsetTop;
hold = true;
};
canvas.onmousemove = function(e) {
if (hold) {
ctx.putImageData(img, 0, 0);
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
ctx.beginPath();
ctx.arc(Math.abs(curX prevX) / 2, Math.abs(curY prevY) / 2, Math.sqrt(Math.pow(curX - prevX, 2) Math.pow(curY - prevY, 2)) / 2, 0, Math.PI * 2, true);
ctx.closePath();
ctx.stroke();
if (fill_value) {
ctx.fill();
}
canvas_data.circle.push({
"startx": prevX,
"starty": prevY,
"radius": curX - prevX,
"thick": ctx.lineWidth,
"stroke": stroke_value,
"stroke_color": ctx.strokeStyle,
"fill": fill_value,
"fill_color": ctx.fillStyle
});
canvas_data.last_action = 3;
}
};
canvas.onmouseup = function(e) {
hold = false;
};
canvas.onmouseout = function(e) {
hold = false;
};
}
// eraser tool
function eraser() {
canvas.onmousedown = function(e) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
hold = true;
prevX = curX;
prevY = curY;
ctx.beginPath();
ctx.moveTo(prevX, prevY);
};
canvas.onmousemove = function(e) {
if (hold) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
draw();
}
};
canvas.onmouseup = function(e) {
hold = false;
};
canvas.onmouseout = function(e) {
hold = false;
};
function draw() {
ctx.lineTo(curX, curY);
var curr_strokeStyle = ctx.strokeStyle;
ctx.strokeStyle = "#ffffff";
ctx.stroke();
canvas_data.pencil.push({
"startx": prevX,
"starty": prevY,
"endx": curX,
"endy": curY,
"thick": ctx.lineWidth,
"color": ctx.strokeStyle
});
canvas_data.last_action = 4;
ctx.strokeStyle = curr_strokeStyle;
}
}
// Function to undo the last action by the user
function undo_pixel() {
// Print that function has been called
console.log("undo_pixel() called");
// Print the last action that was performed
console.log(canvas_data.last_action);
switch (canvas_data.last_action) {
case 0:
case 4:
console.log("Case 0 or 4");
canvas_data.pencil.pop();
canvas_data.last_action = -1;
break;
case 1:
//Undo the last line drawn
console.log("Case 1");
canvas_data.line.pop();
canvas_data.last_action = -1;
break;
case 2:
//Undo the last rectangle drawn
console.log("Case 2");
canvas_data.rectangle.pop();
canvas_data.last_action = -1;
break;
case 3:
//Undo the last circle drawn
console.log("Case 3");
canvas_data.circle.pop();
canvas_data.last_action = -1;
break;
default:
break;
}
// Redraw the canvas
redraw_canvas();
}
// Function to redraw all the shapes on the canvas
function redraw_canvas() {
// Redraw all the shapes on the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the pencil data
canvas_data.pencil.forEach(function(p) {
ctx.beginPath();
ctx.moveTo(p.startx, p.starty);
ctx.lineTo(p.endx, p.endy);
ctx.lineWidth = p.thick;
ctx.strokeStyle = p.color;
ctx.stroke();
});
// Redraw the line data
canvas_data.line.forEach(function(l) {
ctx.beginPath();
ctx.moveTo(l.startx, l.starty);
ctx.lineTo(l.endx, l.endy);
ctx.lineWidth = l.thick;
ctx.strokeStyle = l.color;
ctx.stroke();
});
// Redraw the rectangle data
canvas_data.rectangle.forEach(function(r) {
ctx.beginPath();
ctx.rect(r.startx, r.starty, r.width, r.height);
ctx.lineWidth = r.thick;
ctx.strokeStyle = r.color;
if (r.fill) {
ctx.fillStyle = r.fill_color;
ctx.fillRect(startx, starty, width, height);
}
ctx.stroke();
});
// Redraw the circle data
canvas_data.circle.forEach(function(c) {
// "startx": prevX, "starty": prevY, "radius": curX - prevX, "thick": ctx.lineWidth, "stroke": stroke_value, "stroke_color": ctx.strokeStyle, "fill": fill_value, "fill_color": ctx.fillStyle
ctx.beginPath();
ctx.arc(c.startx, c.starty, c.radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
if (c.fill) {
ctx.fillStyle = c.fill_color;
ctx.fill();
}
});
}
$("#paint1").mousedown(function(e) {
handleMouseDown(e);
});
$("#paint1").mouseup(function(e) {
handleMouseUp(e);
});
$("#paint1").mouseout(function(e) {
handleMouseOut(e);
});
$("#paint1").mousemove(function(e) {
handleMouseMove(e);
});
html {
min-width: 1500px;
position: relative;
}
#toolset {
width: 100px;
height: 340px;
position: absolute;
left: 0px;
top: 50px;
background: #35d128;
}
#paint {
position: absolute;
left: 130px;
top: 50px;
}
#colorset {
position: absolute;
left: 0px;
top: 450px;
width: 300px;
}
#title {
position: absolute;
left: 500px;
}
#penciltool {
background: #358128;
color: #f3f3f3;
width: 80px;
height: 25px;
border: 1px solid #33842a;
-webkit-border-radius: 0 15px 15px 0;
-moz-border-radius: 0 15px 15px 0;
box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
#linetool {
background: #358128;
color: #f3f3f3;
width: 80px;
height: 25px;
border: 1px solid #33842a;
-webkit-border-radius: 0 15px 15px 0;
-moz-border-radius: 0 15px 15px 0;
box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
#rectangletool {
background: #358128;
color: #f3f3f3;
width: 80px;
height: 25px;
border: 1px solid #33842a;
-webkit-border-radius: 0 15px 15px 0;
-moz-border-radius: 0 15px 15px 0;
box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
#circletool {
background: #358128;
color: #f3f3f3;
width: 80px;
height: 25px;
border: 1px solid #33842a;
-webkit-border-radius: 0 15px 15px 0;
-moz-border-radius: 0 15px 15px 0;
box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
#erasertool {
background: #358128;
color: #f3f3f3;
width: 80px;
height: 25px;
border: 1px solid #33842a;
-webkit-border-radius: 0 15px 15px 0;
-moz-border-radius: 0 15px 15px 0;
box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
#resettool {
background: #358128;
color: #f3f3f3;
width: 80px;
height: 25px;
border: 1px solid #33842a;
-webkit-border-radius: 0 15px 15px 0;
-moz-border-radius: 0 15px 15px 0;
box-shadow: rgba(0, 0, 0, .75) 0 2px 6px;
}
<html>
<head>
<title>Paint App</title>
</head>
<body>
<p style="text-align:left; font: bold 35px/35px Georgia, serif;">
PaintApp
<div align="right">
<link rel="stylesheet" type="text/css" href="style.css">
<body onl oad="pencil(`{{ data }}`, `{{ targetx }}`, `{{ targety }}`, `{{ sizex }}`, `{{ sizey }}`)">
<p>
<table>
<tr>
<td>
<fieldset id="toolset" style="margin-top: 3%;">
<br>
<br>
<button id="penciltool" type="button" style="height: 15px; width: 100px;" onclick="pencil()">Pencil</button>
<br>
<br>
<br>
<button id="linetool" type="button" style="height: 15px; width: 100px;" onclick="line()">Line</button>
<br>
<br>
<br>
<button id="rectangletool" type="button" style="height: 15px; width: 100px;" onclick="rectangle()">Rectangle</button>
<br>
<br>
<br>
<button id="circletool" type="button" style="height: 15px; width: 100px;" onclick="circle()">Circle</button>
<br>
<br>
<br>
<button id="erasertool" type="button" style="height: 15px; width: 100px;" onclick="eraser()">Eraser</button>
<br>
<br>
<br>
<button id="resettool" type="button" style="height: 15px; width: 100px;" onclick="reset()">Reset</button>
</fieldset>
</td>
<td>
<canvas id="paint" width="500vw" height="350vw" style="border: 5px solid #000000; margin-top: 3%;"></canvas>
</td>
</tr>
</table>
</p>
<fieldset id="colorset" style="margin-top: 1.8%;">
<table>
<tr>
<td><button style="height: 15px; width: 80px;" onclick="fill()">Fill</button>
<td><button style="background-color: #000000; height: 15px; width: 15px;" onclick="color('#000000')"></button>
<td><button style="background-color: #B0171F; height: 15px; width: 15px;" onclick="color('#B0171F')"></button>
<td><button style="background-color: #DA70D6; height: 15px; width: 15px;" onclick="color('#DA70D6')"></button>
<td><button style="background-color: #8A2BE2; height: 15px; width: 15px;" onclick="color('#8A2BE2')"></button>
<td><button style="background-color: #0000FF; height: 15px; width: 15px;" onclick="color('#0000FF')"></button>
<td><button style="background-color: #4876FF; height: 15px; width: 15px;" onclick="color('#4876FF')"></button>
<td><button style="background-color: #CAE1FF; height: 15px; width: 15px;" onclick="color('#CAE1FF')"></button>
<td><button style="background-color: #6E7B8B; height: 15px; width: 15px;" onclick="color('#6E7B8B')"></button>
<td><button style="background-color: #00C78C; height: 15px; width: 15px;" onclick="color('#00C78C')"></button>
<td><button style="background-color: #00FA9A; height: 15px; width: 15px;" onclick="color('#00FA9A')"></button>
<td><button style="background-color: #00FF7F; height: 15px; width: 15px;" onclick="color('#00FF7F')"></button>
<td><button style="background-color: #00C957; height: 15px; width: 15px;" onclick="color('#00C957')"></button>
<td><button style="background-color: #FFFF00; height: 15px; width: 15px;" onclick="color('#FFFF00')"></button>
<td><button style="background-color: #CDCD00; height: 15px; width: 15px;" onclick="color('#CDCD00')"></button>
<td><button style="background-color: #FFF68F; height: 15px; width: 15px;" onclick="color('#FFF68F')"></button>
<td><button style="background-color: #FFFACD; height: 15px; width: 15px;" onclick="color('#FFFACD')"></button>
<td><button style="background-color: #FFEC8B; height: 15px; width: 15px;" onclick="color('#FFEC8B')"></button>
<td><button style="background-color: #FFD700; height: 15px; width: 15px;" onclick="color('#FFD700')"></button>
<td><button style="background-color: #F5DEB3; height: 15px; width: 15px;" onclick="color('#F5DEB3')"></button>
<td><button style="background-color: #FFE4B5; height: 15px; width: 15px;" onclick="color('#FFE4B5')"></button>
<td><button style="background-color: #EECFA1; height: 15px; width: 15px;" onclick="color('#EECFA1')"></button>
<td><button style="background-color: #FF9912; height: 15px; width: 15px;" onclick="color('#FF9912')"></button>
<td><button style="background-color: #8E388E; height: 15px; width: 15px;" onclick="color('#8E388E')"></button>
<td><button style="background-color: #7171C6; height: 15px; width: 15px;" onclick="color('#7171C6')"></button>
<td><button style="background-color: #7D9EC0; height: 15px; width: 15px;" onclick="color('#7D9EC0')"></button>
<td><button style="background-color: #388E8E; height: 15px; width: 15px;" onclick="color('#388E8E')"></button>
</tr>
<tr>
<td><button style="height: 15px; width: 80px" onclick="outline()">Outline</button>
<td><button style="background-color: #71C671; height: 15px; width: 15px;" onclick="color('#71C671')"></button>
<td><button style="background-color: #8E8E38; height: 15px; width: 15px;" onclick="color('#8E8E38')"></button>
<td><button style="background-color: #C5C1AA; height: 15px; width: 15px;" onclick="color('#C5C1AA')"></button>
<td><button style="background-color: #C67171; height: 15px; width: 15px;" onclick="color('#C67171')"></button>
<td><button style="background-color: #555555; height: 15px; width: 15px;" onclick="color('#555555')"></button>
<td><button style="background-color: #848484; height: 15px; width: 15px;" onclick="color('#848484')"></button>
<td><button style="background-color: #F4F4F4; height: 15px; width: 15px;" onclick="color('#F4F4F4')"></button>
<td><button style="background-color: #EE0000; height: 15px; width: 15px;" onclick="color('#EE0000')"></button>
<td><button style="background-color: #FF4040; height: 15px; width: 15px;" onclick="color('#FF4040')"></button>
<td><button style="background-color: #EE6363; height: 15px; width: 15px;" onclick="color('#EE6363')"></button>
<td><button style="background-color: #FFC1C1; height: 15px; width: 15px;" onclick="color('#FFC1C1')"></button>
<td><button style="background-color: #FF7256; height: 15px; width: 15px;" onclick="color('#FF7256')"></button>
<td><button style="background-color: #FF4500; height: 15px; width: 15px;" onclick="color('#FF4500')"></button>
<td><button style="background-color: #F4A460; height: 15px; width: 15px;" onclick="color('#F4A460')"></button>
<td><button style="background-color: #FF8000; height: 15px; width: 15px;" onclick="color('FF8000')"></button>
<td><button style="background-color: #FFD700; height: 15px; width: 15px;" onclick="color('#FFD700')"></button>
<td><button style="background-color: #8B864E; height: 15px; width: 15px;" onclick="color('#8B864E')"></button>
<td><button style="background-color: #9ACD32; height: 15px; width: 15px;" onclick="color('#9ACD32')"></button>
<td><button style="background-color: #66CD00; height: 15px; width: 15px;" onclick="color('#66CD00')"></button>
<td><button style="background-color: #BDFCC9; height: 15px; width: 15px;" onclick="color('#BDFCC9')"></button>
<td><button style="background-color: #76EEC6; height: 15px; width: 15px;" onclick="color('#76EEC6')"></button>
<td><button style="background-color: #40E0D0; height: 15px; width: 15px;" onclick="color('#40E0D0')"></button>
<td><button style="background-color: #9B30FF; height: 15px; width: 15px;" onclick="color('#9B30FF')"></button>
<td><button style="background-color: #EE82EE; height: 15px; width: 15px;" onclick="color('#EE82EE')"></button>
<td><button style="background-color: #FFC0CB; height: 15px; width: 15px;" onclick="color('#FFC0CB')"></button>
<td><button style="background-color: #7CFC00; height: 15px; width: 15px;" onclick="color('#7CFC00')"></button>
</tr>
<tr>
<td><label>Line Width</label></td>
<td><button id="pixel_plus" type="button" onclick="add_pixel()" style="width: 25px;"> </button></td>
<td><button id="pixel_minus" type="button" onclick="reduce_pixel()" style="width: 25px;">-</button></td>
<td><button id="undo" type="button" onclick="undo_pixel()" style="width: 75px;">Undo</button></td>
</tr>
</table>
<br>
</fieldset>
<script src="//code.jquery.com/jquery-1.8.3.js"></script>
<script src="script.js"></script>
</body>
</html>
Here's what I tried:
First, I made an
undo_pixel()
function that uses thelast_action
variable to pop the last element entered in the respective stack of the previous actionThen I redrew the canvas using a
redraw_canvas()
function that clears the canvas and then redraws it using all the data points stored in thecanvas_data
object.
But what this is causing some unexpected behaviour that I cannot understand entirely. This is what's happening:
I think this is maybe because straight lines are being drawn between all the points being looped through, but I am not exactly sure how else to go about it. How do I correctly implement the redraw/undo function?
CodePudding user response:
The immediate problem, causing the fan shape you observe, is caused by startx
and starty
remaining the same for each object entry in canvas_data.pencil
array. This is because, startx
and starty
are assigned the values held in prevX
and prevY
, but these were set in the mousedown
event listener and are not updated in the mousemove
event listener.
This is fixed as follows:
Firstly, although not strictly necessary for funtion, remove the references to prevX
and prevY
from the mousedown
event listener in the pencil function and set ctx.moveTo(curX, curY)
- this is a little less confusing because the mousedown is where the draw begins from:
// inside pencil function;
canvas.onmousedown = function(e) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
hold = true;
ctx.beginPath();
ctx.moveTo(curX, curY); // prev -> cur;
};
Next, in the mousemove
listener, add lines to update prevX
and prevY
:
// inside pencil function;
canvas.onmousemove = function(e) {
if (hold) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
draw();
prevX = curX; // new
prevY = curY; // new
}
};
These changes form the correct data object array with each object's startx
and starty
value now being set to the previous object's endx
and endy
value, as required.
However, there's another immediate problem: the undo_pixel
function only fully executes on the first click to remove the last object of the pencil array (causing the last sliver of line captured by the last mousemove
event to disappear as intended), but subsequent clicks (to remove successive parts of the line) are aborted.
I'm assuming the undo_pixel
function is intended to remove a sliver for each click rather than the entire pencil line (see later if not).
The reason for the process to be aborted is because of resetting the canvas_data.last_action
flag inside undo_pixel
:
canvas_data.last_action = -1`
Because there is no case
block in the switch statement for -1
, nothing happens on subsequent clicks of the undo
button;
Instead, the case block for the pencil should be:
case 4:
canvas_data.pencil.pop();
canvas_data.last_action = 4; // not -1;
break;
You have to leave it at 4 until another action is selected to undo or until the whole pencil line is removed and then move to the object drawn immediately before the lencil line (for which you will need to keep track of the order in which items were drawn).
I've made a working snippet with the above suggestions. You'll need to display it full page as the preview window is too small to see the line while you click the undo button. If you draw a short pencil sqiggle and repeatedly press the undo button, you'll see the line being 'undrawn'.
(I had to delete some of your other functions as I exceeded the allowed character limit for an answer)
var canvas = document.getElementById("paint");
var ctx = canvas.getContext("2d");
var pi2 = Math.PI * 2;
var resizerRadius = 8;
var rr = resizerRadius * resizerRadius;
var width = canvas.width;
var height = canvas.height;
var curX, curY, prevX, prevY;
var hold = false;
ctx.lineWidth = 2;
var fill_value = true;
var stroke_value = false;
var canvas_data = {
"pencil": [],
"line": [],
"rectangle": [],
"circle": [],
"eraser": [],
"last_action": -1
};
// //connect to postgres client
// var pg = require('pg');
// var conString = "postgres://postgres:database1@localhost:5432/sketch2photo";
// client = new pg.Client(conString);
function color(color_value) {
ctx.strokeStyle = color_value;
ctx.fillStyle = color_value;
}
function add_pixel() {
ctx.lineWidth = 1;
}
function reduce_pixel() {
if (ctx.lineWidth == 1) {
ctx.lineWidth = 1;
} else {
ctx.lineWidth -= 1;
}
}
function fill() {
fill_value = true;
stroke_value = false;
}
function outline() {
fill_value = false;
stroke_value = true;
}
function reset() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas_data = {
"pencil": [],
"line": [],
"rectangle": [],
"circle": [],
"eraser": [],
"last_action": -1
};
}
// pencil tool
function pencil(data, targetX, targetY, targetWidth, targetHeight) {
//prevX = 0; // new
//prevY = 0; // new
canvas.onmousedown = function(e) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
hold = true;
ctx.beginPath();
ctx.moveTo(curX, curY); // prev -> cur;
};
canvas.onmousemove = function(e) {
if (hold) {
curX = e.clientX - canvas.offsetLeft;
curY = e.clientY - canvas.offsetTop;
draw();
prevX = curX; // new
prevY = curY; // new
}
};
canvas.onmouseup = function(e) {
hold = false;
};
canvas.onmouseout = function(e) {
hold = false;
};
function draw() {
ctx.lineTo(curX, curY);
ctx.stroke();
canvas_data.pencil.push({
"startx": prevX,
"starty": prevY,
"endx": curX,
"endy": curY,
"thick": ctx.lineWidth,
"color": ctx.strokeStyle
});
canvas_data.last_action = 0;
}
}
function undo_pixel() {
switch (canvas_data.last_action) {
case 0:
case 4:
canvas_data.pencil.pop();
canvas_data.last_action = 4; // not -1;
break;
case 1:
//Undo the last line drawn
console.log("Case 1");
canvas_data.line.pop();
canvas_data.last_action = -1;
break;
case 2:
//Undo the last rectangle drawn
console.log("Case 2");
canvas_data.rectangle.pop();
canvas_data.last_action = -1;
break;
case 3:
//Undo the last circle drawn
console.log("Case 3");
canvas_data.circle.pop();
canvas_data.last_action = -1;
break;
default:
break;
}
redraw_canvas();
}
function redraw_canvas() {
// Redraw all the shapes on the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the pencil data
canvas_data.pencil.forEach(function(p) {
ctx.beginPath();
ctx.moveTo(p.startx, p.starty);
ctx.lineTo(p.endx, p.endy);
ctx.lineWidth = p.thick;
ctx.strokeStyle = p.color;
ctx.stroke();
});
// Redraw the line data
canvas_data.line.forEach(function(l) {
ctx.beginPath();
ctx.moveTo(l.startx, l.starty);
ctx.lineTo(l.endx, l.endy);
ctx.lineWidth = l.thick;
ctx.strokeStyle = l.color;
ctx.stroke();
});
}
<html>
<head>
<title>Paint App</title>
<script src="main.js" defer></script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<body>
<p style="text-align:left; font: bold 35px/35px Georgia, serif;">
PaintApp
</body>
</body>
<div align="right">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<body onl oad="pencil(`{{ data }}`, `{{ targetx }}`, `{{ targety }}`, `{{ sizex }}`, `{{ sizey }}`)">
<p>
<table>
<tr>
<td>
<fieldset id="toolset" style="margin-top: 3%;">
<br>
<br>
<button id="penciltool" type="button" style="height: 15px; width: 100px;" onclick="pencil()">Pencil</button>
<br>
<br>
<br>
<button id="linetool" type="button" style="height: 15px; width: 100px;" onclick="line()">Line</button>
<br>
<br>
<br>
<button id="rectangletool" type="button" style="height: 15px; width: 100px;" onclick="rectangle()">Rectangle</button>
<br>
<br>
<br>
<button id="circletool" type="button" style="height: 15px; width: 100px;" onclick="circle()">Circle</button>
<br>
<br>
<br>
<button id="erasertool" type="button" style="height: 15px; width: 100px;" onclick="eraser()">Eraser</button>
<br>
<br>
<br>
<button id="resettool" type="button" style="height: 15px; width: 100px;" onclick="reset()">Reset</button>
</fieldset>
</td>
<td>
<canvas id="paint" width="500vw" height="350vw" style="border: 5px solid #000000; margin-top: 3%;"></canvas>
</td>
</tr>
</table>
</p>
<fieldset id="colorset" style="margin-top: 1.8%;">
<table>
<tr>
<td><button style="height: 15px; width: 80px;" onclick="fill()">Fill</button>
<td><button style="background-color: #000000; height: 15px; width: 15px;" onclick="color('#000000')"></button>
<td><button style="background-color: #B0171F; height: 15px; width: 15px;" onclick="color('#B0171F')"></button>
<td><button style="background-color: #DA70D6; height: 15px; width: 15px;" onclick="color('#DA70D6')"></button>
<td><button style="background-color: #8A2BE2; height: 15px; width: 15px;" onclick="color('#8A2BE2')"></button>
<td><button style="background-color: #0000FF; height: 15px; width: 15px;" onclick="color('#0000FF')"></button>
<td><button style="background-color: #4876FF; height: 15px; width: 15px;" onclick="color('#4876FF')"></button>
<td><button style="background-color: #CAE1FF; height: 15px; width: 15px;" onclick="color('#CAE1FF')"></button>
<td><button style="background-color: #6E7B8B; height: 15px; width: 15px;" onclick="color('#6E7B8B')"></button>
<td><button style="background-color: #00C78C; height: 15px; width: 15px;" onclick="color('#00C78C')"></button>
<td><button style="background-color: #00FA9A; height: 15px; width: 15px;" onclick="color('#00FA9A')"></button>
<td><button style="background-color: #00FF7F; height: 15px; width: 15px;" onclick="color('#00FF7F')"></button>
<td><button style="background-color: #00C957; height: 15px; width: 15px;" onclick="color('#00C957')"></button>
<td><button style="background-color: #FFFF00; height: 15px; width: 15px;" onclick="color('#FFFF00')"></button>
<td><button style="background-color: #CDCD00; height: 15px; width: 15px;" onclick="color('#CDCD00')"></button>
<td><button style="background-color: #FFF68F; height: 15px; width: 15px;" onclick="color('#FFF68F')"></button>
<td><button style="background-color: #FFFACD; height: 15px; width: 15px;" onclick="color('#FFFACD')"></button>
<td><button style="background-color: #FFEC8B; height: 15px; width: 15px;" onclick="color('#FFEC8B')"></button>
<td><button style="background-color: #FFD700; height: 15px; width: 15px;" onclick="color('#FFD700')"></button>
<td><button style="background-color: #F5DEB3; height: 15px; width: 15px;" onclick="color('#F5DEB3')"></button>
<td><button style="background-color: #FFE4B5; height: 15px; width: 15px;" onclick="color('#FFE4B5')"></button>
<td><button style="background-color: #EECFA1; height: 15px; width: 15px;" onclick="color('#EECFA1')"></button>
<td><button style="background-color: #FF9912; height: 15px; width: 15px;" onclick="color('#FF9912')"></button>
<td><button style="background-color: #8E388E; height: 15px; width: 15px;" onclick="color('#8E388E')"></button>
<td><button style="background-color: #7171C6; height: 15px; width: 15px;" onclick="color('#7171C6')"></button>
<td><button style="background-color: #7D9EC0; height: 15px; width: 15px;" onclick="color('#7D9EC0')"></button>
<td><button style="background-color: #388E8E; height: 15px; width: 15px;" onclick="color('#388E8E')"></button>
</tr>
<tr>
<td><button style="height: 15px; width: 80px" onclick="outline()">Outline</button>
<td><button style="background-color: #71C671; height: 15px; width: 15px;" onclick="color('#71C671')"></button>
<td><button style="background-color: #8E8E38; height: 15px; width: 15px;" onclick="color('#8E8E38')"></button>
<td><button style="background-color: #C5C1AA; height: 15px; width: 15px;" onclick="color('#C5C1AA')"></button>
<td><button style="background-color: #C67171; height: 15px; width: 15px;" onclick="color('#C67171')"></button>
<td><button style="background-color: #555555; height: 15px; width: 15px;" onclick="color('#555555')"></button>
<td><button style="background-color: #848484; height: 15px; width: 15px;" onclick="color('#848484')"></button>
<td><button style="background-color: #F4F4F4; height: 15px; width: 15px;" onclick="color('#F4F4F4')"></button>
<td><button style="background-color: #EE0000; height: 15px; width: 15px;" onclick="color('#EE0000')"></button>
<td><button style="background-color: #FF4040; height: 15px; width: 15px;" onclick="color('#FF4040')"></button>
<td><button style="background-color: #EE6363; height: 15px; width: 15px;" onclick="color('#EE6363')"></button>
<td><button style="background-color: #FFC1C1; height: 15px; width: 15px;" onclick="color('#FFC1C1')"></button>
<td><button style="background-color: #FF7256; height: 15px; width: 15px;" onclick="color('#FF7256')"></button>
<td><button style="background-color: #FF4500; height: 15px; width: 15px;" onclick="color('#FF4500')"></button>
<td><button style="background-color: #F4A460; height: 15px; width: 15px;" onclick="color('#F4A460')"></button>
<td><button style="background-color: #FF8000; height: 15px; width: 15px;" onclick="color('FF8000')"></button>
<td><button style="background-color: #FFD700; height: 15px; width: 15px;" onclick="color('#FFD700')"></button>
<td><button style="background-color: #8B864E; height: 15px; width: 15px;" onclick="color('#8B864E')"></button>
<td><button style="background-color: #9ACD32; height: 15px; width: 15px;" onclick="color('#9ACD32')"></button>
<td><button style="background-color: #66CD00; height: 15px; width: 15px;" onclick="color('#66CD00')"></button>
<td><button style="background-color: #BDFCC9; height: 15px; width: 15px;" onclick="color('#BDFCC9')"></button>
<td><button style="background-color: #76EEC6; height: 15px; width: 15px;" onclick="color('#76EEC6')"></button>
<td><button style="background-color: #40E0D0; height: 15px; width: 15px;" onclick="color('#40E0D0')"></button>
<td><button style="background-color: #9B30FF; height: 15px; width: 15px;" onclick="color('#9B30FF')"></button>
<td><button style="background-color: #EE82EE; height: 15px; width: 15px;" onclick="color('#EE82EE')"></button>
<td><button style="background-color: #FFC0CB; height: 15px; width: 15px;" onclick="color('#FFC0CB')"></button>
<td><button style="background-color: #7CFC00; height: 15px; width: 15px;" onclick="color('#7CFC00')"></button>
</tr>
<tr>
<td><label>Line Width</label></td>
<td><button id="pixel_plus" type="button" onclick="add_pixel()" style="width: 25px;"> </button></td>
<td><button id="pixel_minus" type="button" onclick="reduce_pixel()" style="width: 25px;">-</button></td>
<td><button id="undo" type="button" onclick="undo_pixel()" style="width: 75px;">Undo</button></td>
</tr>
</table>
<br>
</fieldset>
<script src="//code.jquery.com/jquery-1.8.3.js"></script>
<script src=" {{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
Note about intended undo function
If I have misunderstood and the undo
action is supposed to remove the entire last pencil shape for a single click, you have to change the way the pencil
object array is structured.
At present, the array is structured like this:
[
{
"startx": 148,
"starty": 281,
"endx": 148,
"endy": 280,
"thick": 2,
"color": "#000000"
},
// more data objects for each mousemove captured;
{
"startx": 148,
"starty": 281,
"endx": 148,
"endy": 280,
"thick": 2,
"color": "#000000"
}
]
this means that removing the last object (which your .pop
in case 4
does) removes only the sliver of the pencil line captured during the last mousemove event. This is ok if you want to fine tune a line (I think that's a good feature, and assuemed it you wanted it that way) but causes a problem if more than one separate pencil line is drawn. If there are two pencil lines, they are merged into one inside the pencil
data array.
To deal with this you would have to re-structure the pencil
array to hold descrete inner arrays for each line like this:
[
// first line array:
[
{
"startx": 148,
"starty": 281,
"endx": 148,
"endy": 280,
"thick": 2,
"color": "#000000"
},
// more data objects for each mousemove captured;
{
"startx": 148,
"starty": 281,
"endx": 148,
"endy": 280,
"thick": 2,
"color": "#000000"
}
],
// more line arrays;
// last line array:
[
{
"startx": 148,
"starty": 281,
"endx": 148,
"endy": 280,
"thick": 2,
"color": "#000000"
},
// more data objects for each mousemove captured;
{
"startx": 148,
"starty": 281,
"endx": 148,
"endy": 280,
"thick": 2,
"color": "#000000"
}
]
] // end of pencil array;
Under this structure, .pop
will remove a whole line (which may be what you intended). It will also solve a bug where currently a redraw will join any separate lines into one line.