Javascript 使用画布撤消/重做绘制程序

Javascript 使用画布撤消/重做绘制程序,javascript,jquery,canvas,Javascript,Jquery,Canvas,我需要为我的绘画程序实现撤销/重做系统: 我提出的想法是有两个数组堆栈,一个用于撤销,一个用于重做。 无论何时绘制并释放鼠标,它都会通过推操作将画布图像保存到撤消数组堆栈中。如果您绘制其他内容并释放,它也会这样做。但是,如果单击“撤消”,它将弹出撤消数组的顶部图像,并将其打印到画布上,然后将其推送到重做堆栈上 单击时“重做”将从其自身弹出并按下“撤消”。每次鼠标关闭后,将打印“撤消”的顶部 这是正确的方法还是有更好的方法?试着实现 这里还有一个类似的问题:这是我为自己的事业所做的基本想法;它确实

我需要为我的绘画程序实现撤销/重做系统:

我提出的想法是有两个数组堆栈,一个用于撤销,一个用于重做。 无论何时绘制并释放鼠标,它都会通过推操作将画布图像保存到撤消数组堆栈中。如果您绘制其他内容并释放,它也会这样做。但是,如果单击“撤消”,它将弹出撤消数组的顶部图像,并将其打印到画布上,然后将其推送到重做堆栈上

单击时“重做”将从其自身弹出并按下“撤消”。每次鼠标关闭后,将打印“撤消”的顶部

这是正确的方法还是有更好的方法?

试着实现


这里还有一个类似的问题:

这是我为自己的事业所做的基本想法;它确实工作得很好,但这种方法可能会占用大量内存

所以我做的一个小小的调整就是只存储用户上次操作大小的撤销/重做片段。因此,如果他们只画了画布的一小部分,你可以存储一个小画布,它是整个画布的一小部分,并节省大量内存

我的撤消/重做系统位于中。我两年前写过这个应用程序,所以我的记忆有点模糊,但如果你决定解码我所做的事情,我可以帮助解释一些事情。

一句警告

将整个画布保存为图像以进行撤消/重做会占用大量内存,而且会降低性能。

但是,将用户的图形逐步保存到阵列中仍然是一个好主意

与其将整个画布保存为图像,不如创建一个点数组来记录用户在绘制时所做的每个鼠标移动。这是您的“绘图数组”,可用于完全重新绘制画布

每当用户拖动鼠标时,他们都在创建一条多段线(一组连接的线段)。当用户拖动以创建直线时,请将该鼠标移动点保存到图形阵列,并将其多段线延伸到当前鼠标移动位置

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();
}
重做在逻辑上更复杂。

最简单的重做是用户只能在撤消后立即重做。在单独的“重做”数组中保存每个“撤消”点。然后,如果用户想要重做,只需将重做位添加回主阵列

复杂的是,如果在用户完成更多绘图后让用户“重做”

例如,你可能会得到一条有两条尾巴的狗:一条新画的尾巴和第二条“重做”的尾巴

因此,如果在附加绘图之后允许重做,则需要一种方法来防止用户在重做过程中感到困惑。马特·格里尔“分层”重做的想法是一个好方法。只需通过保存重做点而不是整个画布图像来改变想法。然后,用户可以打开/关闭重做,以查看是否希望继续重做

下面是使用我为上一个问题创建的撤消数组的示例:

下面是代码和小提琴:


正文{背景色:象牙;}
画布{边框:1px纯红;}
$(函数(){
var canvas=document.getElementById(“canvas”);
var ctx=canvas.getContext(“2d”);
var lastX;
血管成形术;
var mouseX;
var mouseY;
var canvasOffset=$(“#画布”).offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isMouseDown=错误;
var-brushSize=20;
var brushColor=“#ff0000”;
var点=[];
功能手柄向下(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(例如clientY-offsetY);
//把你的鼠标下的东西放在这里
ctx.beginPath();
如果(ctx.lineWidth!=brushSize){ctx.lineWidth=brushSize;}
如果(ctx.strokeStyle!=brushColor){ctx.strokeStyle=brushColor;}
ctx.moveTo(mouseX,mouseY);
push({x:mouseX,y:mouseY,size:brushSize,color:brushColor,mode:“begin”});
lastX=鼠标;
拉蒂=老鼠;
isMouseDown=真;
}
功能handleMouseUp(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(例如clientY-offsetY);
//把你的鼠标放在这里
isMouseDown=错误;
push({x:mouseX,y:mouseY,大小:brushSize,颜色:brushColor,模式:“end”});
}
功能手柄移动(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(例如clientY-offsetY);
//把你的mousemove放在这里
如果(isMouseDown){
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
lastX=鼠标;
拉蒂=老鼠;
//命令模式材料
push({x:mouseX,y:mouseY,大小:brushSize,颜色:brushColor,模式:“draw”});
}
}
函数重画(){
如果(points.length==0){return;}
clearRect(0,0,canvas.width,canvas.height);

对于(var i=0;iYou可以尝试使用,它允许自由绘制每个形状并将其包装到对象中(请参阅),这将使其更简单,以便在撤消堆栈上保存新操作时不要忘记清除重做堆栈。保存整个图像可能会占用大量内存。您可以限制堆栈大小,或者尝试仅保存图像之间的更改(基本上是每一笔)。是的,每一笔都是我想要的方式,可能有一个从0到9的10个堆栈。我似乎无法让它工作,但是:/我正在跟踪我发现的这个,但似乎与我的代码不起作用,你可能会想什么原因?+1我喜欢你的分层想法…这是一个帮助用户保持组织的好方法。有问题的链接现在已经断开了。我所以考虑了这种方法,但在我看来,当你添加更多的工具时,比如一个填充桶,它的伸缩性就不好。老实说,尽管本页提到的系统没有一个能够很好地伸缩,比如当你超越了对工具的撤销/重做(例如,撤销删除一层),这个系统会崩溃很多。我发现了困难的方法:)@马特格里尔:您好……很高兴认识您!如果您添加了
<!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>