Я делаю приложение для рисования в HTML, используя JavaScript и Flask-Python. В настоящее время я могу без проблем рисовать множество карандашных рисунков и фигур, таких как прямоугольники/круги. Следующая функциональность, которую я пытаюсь реализовать для этого приложения, — это функция отменить.
Я храню штрихи и данные рисования холста в объекте JS следующим образом:
canvas_data = { "pencil": [], "line": [], "rectangle": [], "circle": [], "eraser": [], "last_action": -1 };
Все названия клавиш должны быть понятными, кроме last_action
. Я использую эту переменную last_action
, чтобы узнать последнюю категорию, использованную пользователем, чтобы позже я мог использовать эту информацию для реализации функции отмены.
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.info("undo_pixel() called");
// Print the last action that was performed
console.info(canvas_data.last_action);
switch (canvas_data.last_action) {
case 0:
case 4:
console.info("Case 0 or 4");
canvas_data.pencil.pop();
canvas_data.last_action = -1;
break;
case 1:
//Undo the last line drawn
console.info("Case 1");
canvas_data.line.pop();
canvas_data.last_action = -1;
break;
case 2:
//Undo the last rectangle drawn
console.info("Case 2");
canvas_data.rectangle.pop();
canvas_data.last_action = -1;
break;
case 3:
//Undo the last circle drawn
console.info("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 onload = "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>
Вот что я пробовал:
Во-первых, я создал функцию undo_pixel()
, которая использует переменную last_action
для извлечения последнего элемента, введенного в соответствующий стек предыдущего действия.
Затем я перерисовываю холст с помощью функции redraw_canvas()
, которая очищает холст, а затем перерисовывает его, используя все точки данных, хранящиеся в объекте canvas_data
.
Но что это вызывает какое-то неожиданное поведение, которое я не могу понять полностью. Вот что происходит:
Я думаю, что это может быть связано с тем, что между всеми точками, проходящими через цикл, рисуются прямые линии, но я не совсем уверен, как еще это сделать. Как правильно реализовать функцию перерисовки/отмены?
Спасибо за ваше предложение, я добавил ссылку на скрипт JS. Дайте мне знать, если я должен добавить что-нибудь еще.
Теперь я добавил его с помощью инструмента codenippet.
Не связано: похоже, вы используете какой-то механизм шаблонов ({{ ... }}
), который, вероятно, не работает во фрагменте стека. Также в вашем HTML есть два элемента body
, которые делают его недействительным. При нажатии «Отменить» возникает ошибка, связанная с startx
. Вы должны сначала исправить эту ошибку. Наконец, отмена никогда не будет работать правильно, если у вас нет стека действий все. Наличие ссылки только на последнее действие сделает невозможным отмену двух действий.
Привет, по вашим первым двум пунктам я исправил тег body и удалил строки, которые могут вызывать проблему с зависимостями, но для меня это работает так же, как и раньше, во фрагменте кода, а также в ссылке JS Fiddle. Это мой первый раз, когда я использую функцию фрагмента кода здесь, поэтому я могу ошибаться, но у меня она работает нормально (я также не получаю никаких ошибок относительно startx), дайте мне знать, если я делаю что-то неправильно. Наконец, я понимаю вашу точку зрения относительно невозможности отменить более одного действия, это изменит это. Но почему это все еще не работает только для одного действия?
В блоке, который следует сразу за этим комментарием «Перерисовать данные карандашом», я думаю, вы пропустили closePath
после stroke
. Ваше изображение выглядит так, как обычно, когда возникает проблема с началом или окончанием пути обводки.
У меня есть рабочий ответ, но фрагмент продолжает удалять css, потому что все слишком длинное. Это тот случай, когда минимальный рабочий пример очень помог бы. Я немного подрежу вам код, чтобы фрагмент заработал, но остальная часть ответа остается в силе.
Непосредственная проблема, вызывающая форму веера, которую вы наблюдаете, вызвана тем, что startx
и starty
остаются одинаковыми для каждой записи объекта в массиве canvas_data.pencil
. Это связано с тем, что startx
и starty
присваиваются значения, хранящиеся в prevX
и prevY
, но они были установлены в прослушивателе событий mousedown
и не обновляются в прослушивателе событий mousemove
.
Это фиксируется следующим образом:
Во-первых, хотя это и не является строго необходимым для работы, удалите ссылки на prevX
и prevY
из прослушивателя событий mousedown
в функции карандаша и установите ctx.moveTo(curX, curY)
— это немного менее запутанно, потому что отрисовка начинается с нажатия мыши:
// 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;
};
Далее в слушателе mousemove
добавьте строки для обновления prevX
и 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
}
};
Эти изменения формируют правильный массив объектов данных, при этом значения startx
и starty
каждого объекта теперь устанавливаются равными значениям endx
и endy
предыдущего объекта, как требуется.
Однако есть еще одна непосредственная проблема: функция undo_pixel
полностью выполняется только при первом щелчке, чтобы удалить последний объект массива карандашей (в результате чего последняя полоска строки, захваченная последним событием mousemove
, исчезает, как предполагалось), но последующие щелчки (для удалить последовательные части строки) прерываются.
Я предполагаю, что функция undo_pixel
предназначена для удаления полоски для каждого щелчка, а не всей линии карандаша (см. Позже, если нет).
Причиной прерывания процесса является сброс флага canvas_data.last_action
внутри undo_pixel
:
canvas_data.last_action = -1`
Поскольку в операторе switch для case
нет блока -1
, при последующих нажатиях кнопки undo
ничего не происходит;
Вместо этого футляр для карандаша должен быть:
case 4:
canvas_data.pencil.pop();
canvas_data.last_action = 4; // not -1;
break;
Вы должны оставить его на 4 до тех пор, пока не будет выбрано другое действие для отмены или пока вся линия карандаша не будет удалена, а затем перейти к объекту, нарисованному непосредственно перед линией линзы (для чего вам нужно будет отслеживать порядок, в котором элементы были нарисовано).
Я сделал рабочий фрагмент с приведенными выше предложениями. Вам нужно будет отобразить его на всю страницу, так как окно предварительного просмотра слишком маленькое, чтобы увидеть строку, когда вы нажимаете кнопку отмены. Если вы нарисуете короткую завитушку карандашом и несколько раз нажмете кнопку отмены, вы увидите, что линия «не рисуется».
(Мне пришлось удалить некоторые из ваших других функций, так как я превысил разрешенный лимит символов для ответа)
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.info("Case 1");
canvas_data.line.pop();
canvas_data.last_action = -1;
break;
case 2:
//Undo the last rectangle drawn
console.info("Case 2");
canvas_data.rectangle.pop();
canvas_data.last_action = -1;
break;
case 3:
//Undo the last circle drawn
console.info("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 onload = "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>
Примечание о предполагаемой функции отмены
Если я неправильно понял и действие undo
должно удалить всю последнюю фигуру карандаша за один клик, вам нужно изменить способ структурирования массива объектов pencil
.
В настоящее время массив имеет следующую структуру:
[
{
"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"
}
]
это означает, что удаление последнего объекта (что делает ваш .pop
в case 4
) удаляет только полоску карандашной линии, захваченную во время последнего события mousemove. Это нормально, если вы хотите точно настроить линию (я думаю, что это хорошая функция, и предположил, что вы этого хотели), но вызывает проблему, если нарисовано более одной отдельной карандашной линии. Если есть две линии карандаша, они объединяются в одну внутри массива данных pencil
.
Чтобы справиться с этим, вам придется реструктурировать массив pencil
, чтобы он содержал дискретные внутренние массивы для каждой строки следующим образом:
[
// 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;
В этой структуре .pop
удалит целую строку (что может быть тем, что вы хотели). Это также устранит ошибку, из-за которой в настоящее время перерисовка соединяет любые отдельные строки в одну строку.
Большое спасибо за подробный ответ и подробное объяснение, это мне очень помогло. Я действительно намеревался использовать .pop
для удаления всей строки, поэтому сейчас я работаю над изменением типа данных, чтобы он был таким, как вы упомянули в ответе. Я столкнулся с ошибкой, о которой вы упомянули, когда перерисовка объединяет любые отдельные строки в одну строку, я надеюсь, что это тоже будет решено. Спасибо еще раз.
Удачи, я попытался удалить большую часть ненужного кода, но просто не могу заставить его сохранить стили, поэтому фрагмент не очень хорош. Я думаю, что вы поняли идею, хотя. Благодарю.
Вероятно, вам лучше предоставить рабочий пример с помощью инструмента codesnippet здесь.