问题描述
我需要为我的绘图程序实现一个撤消/重做系统:
I need to implement a undo/redo system for my paint program: http://www.taffatech.com/Paint.html
我想出的想法是拥有2个阵列堆栈,一个用于撤销,一个用于重做。
无论何时绘制和释放鼠标,它都会通过推送将画布图像保存到撤消数组堆栈。如果你绘制其他东西并释放它也会做同样的事情。但是,如果单击撤消,它将弹出撤消数组的顶部图像并将其打印到画布,然后将其推送到重做堆栈。
My idea that I came up with is to have 2 arrays stacks, one for undo and 1 for redo.Anytime you draw and release the mouse it saves the canvas image to the undo array stack by push. if you draw something else and release it will do the same. However if you click undo it will pop the top image of undo array and print that to canvas and then push it onto the redo stack.
重做时单击将从其自身弹出并推动撤消。每次关闭鼠标后都会打印撤消的顶部。
redo when clicked will pop from itself and push to undo. the top of undo will be printed after each mouse off.
这是正确的方式还是有更好的方法?
Is this the right way or is there a better one?
推荐答案
警告!
将整个画布保存为撤消/重做的图像是内存密集型和性能杀手。
但是,您逐步将用户的绘图保存在数组中的想法仍然是个好主意。
However, your idea of progressively saving the user’s drawings in an array is still a good idea.
不是将整个画布保存为图像,而是创建一个点阵列来记录用户在绘制时所做的每个鼠标移动。这是你的绘图数组,可用于完全重绘画布。
Instead of saving the whole canvas as an image, just create an array of points to record every mousemove the user makes as they are drawing. This is your "drawing array" that can be used to fully redraw your canvas.
每当用户拖动鼠标时,他们就会创建折线(一组连接的线段) )。当用户拖动创建一条线时,将该鼠标移动点保存到绘图数组并将其折线延伸到当前鼠标移动位置。
Whenever the user drags the mouse they are creating a polyline (a group of connected line segments). When the user drags to create a line, save that mousemove point to your drawing array and extend their polyline to the current mousemove position.
function handleMouseMove(e) {
// calc where the mouse is on the canvas
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// if the mouse is being dragged (mouse button is down)
// then keep drawing a polyline to this new mouse position
if (isMouseDown) {
// extend the polyline
ctx.lineTo(mouseX, mouseY);
ctx.stroke();
// save this x/y because we might be drawing from here
// on the next mousemove
lastX = mouseX;
lastY = mouseY;
// Command pattern stuff: Save the mouse position and
// the size/color of the brush to the "undo" array
points.push({
x: mouseX,
y: mouseY,
size: brushSize,
color: brushColor,
mode: "draw"
});
}
}
如果用户想撤消 ,只需弹出绘图数组的最后一点:
function undoLastPoint() {
// remove the last drawn point from the drawing array
var lastPoint=points.pop();
// add the "undone" point to a separate redo array
redoStack.unshift(lastPoint);
// redraw all the remaining points
redrawAll();
}
重做在逻辑上更棘手。
最简单的重做是用户只能在撤消后立即重做。将每个撤消点保存在单独的重做数组中。然后,如果用户想要重做,你可以将重做位添加回主阵列。
The simplest Redo is when the user can only redo immediately after an undo. Save each "undo" point in your separate "redo" array. Then if the user wants to redo, you can just add the redo bits back to the to the main array.
如果你让用户重做之后的复杂性他们做了更多的绘画。
The complication is if you let the user "redo" after they have done more drawing.
例如,你最终可能会得到一条有2条尾巴的狗:一条新画的尾巴和第二条重做的尾巴!
For example, you could end up with a dog with 2 tails: a newly drawn tail and a second "redo" tail !
因此,如果您在额外绘图后允许重做,则需要一种方法来防止用户在重做期间感到困惑。 Matt Greer关于分层redos的想法是一个很好的方法。只需通过保存重做点来改变这个想法,而不是整个画布图像。然后用户可以打开/关闭重做以查看他们是否想要保留他们的重做。
So if you allow redo’s after additional drawing, you would need a way to keep the user from being confused during redo. Matt Greer’s idea of "layering" redos is one good way. Just alter that idea by saving the redo points, not the entire canvas image. Then the user could toggle the redo on/off to see if they would like to keep their redo.
这是使用我为之前创建的撤消数组的示例问题:
Here is an example of using an undo array I created for a previous question: Drawing to canvas like in paint
这是代码和小提琴:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<!--[if lt IE 9]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var lastX;
var lastY;
var mouseX;
var mouseY;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isMouseDown=false;
var brushSize=20;
var brushColor="#ff0000";
var points=[];
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
ctx.beginPath();
if(ctx.lineWidth!=brushSize){ctx.lineWidth=brushSize;}
if(ctx.strokeStyle!=brushColor){ctx.strokeStyle=brushColor;}
ctx.moveTo(mouseX,mouseY);
points.push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:"begin"});
lastX=mouseX;
lastY=mouseY;
isMouseDown=true;
}
function handleMouseUp(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isMouseDown=false;
points.push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:"end"});
}
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
if(isMouseDown){
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
lastX=mouseX;
lastY=mouseY;
// command pattern stuff
points.push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:"draw"});
}
}
function redrawAll(){
if(points.length==0){return;}
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<points.length;i++){
var pt=points[i];
var begin=false;
if(ctx.lineWidth!=pt.size){
ctx.lineWidth=pt.size;
begin=true;
}
if(ctx.strokeStyle!=pt.color){
ctx.strokeStyle=pt.color;
begin=true;
}
if(pt.mode=="begin" || begin){
ctx.beginPath();
ctx.moveTo(pt.x,pt.y);
}
ctx.lineTo(pt.x,pt.y);
if(pt.mode=="end" || (i==points.length-1)){
ctx.stroke();
}
}
ctx.stroke();
}
function undoLast(){
points.pop();
redrawAll();
}
ctx.lineJoin = "round";
ctx.fillStyle=brushColor;
ctx.lineWidth=brushSize;
$("#brush5").click(function(){ brushSize=5; });
$("#brush10").click(function(){ brushSize=10; });
// Important! Brush colors must be defined in 6-digit hex format only
$("#brushRed").click(function(){ brushColor="#ff0000"; });
$("#brushBlue").click(function(){ brushColor="#0000ff"; });
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
// hold down the undo button to erase the last line segment
var interval;
$("#undo").mousedown(function() {
interval = setInterval(undoLast, 100);
}).mouseup(function() {
clearInterval(interval);
});
}); // end $(function(){});
</script>
</head>
<body>
<p>Drag to draw. Use buttons to change lineWidth/color</p>
<canvas id="canvas" width=300 height=300></canvas><br>
<button id="undo">Hold this button down to Undo</button><br><br>
<button id="brush5">5px Brush</button>
<button id="brush10">10px Brush</button>
<button id="brushRed">Red Brush</button>
<button id="brushBlue">Blue Brush</button>
</body>
</html>
这篇关于使用Canvas撤消/重做绘画程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!