撤消/重做的油漆程序 - 画布 [英] Undo/Redo for paint program - Canvas

查看:159
本文介绍了撤消/重做的油漆程序 - 画布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要为我的绘画程序实施撤消/重做系统: http://www.taffatech。 com / Paint.html



我的想法是我想出了2个数组堆栈,一个用于撤销,一个用于重做。
每当你绘制和释放鼠标,它通过push将画布图像保存到undo数组堆栈。如果你画别的东西,释放它会做同样的。然而,如果你点击撤消,它会弹出undo数组的顶部图像并打印到画布,然后将其推送到重做堆栈。



重做当点击将从自身弹出并推送到撤消。



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

解决方案

警告词



将整个画布保存为撤消/



但是,您仍然希望将用户的绘图逐步保存在数组中。



不是将整个画布保存为图像,而是创建一个点数组,以记录用户在绘制时所做的每个鼠标移动。这是你的绘图数组,可以用来完全重绘你的画布。



每当用户拖动鼠标,他们正在创建一个多段线)。当用户拖动以创建线时,将该mousemove点保存到绘图数组并将其折线扩展到当前mousemove位置。

  function handleMouseMove(e){

// calc其中鼠标在画布上
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);

//如果鼠标被拖动(鼠标按钮被关闭)
//然后继续画一个折线到这个新的鼠标位置
if(isMouseDown){

//扩展折线
ctx.lineTo(mouseX,mouseY);
ctx.stroke();

//保存这个x / y,因为我们可能从这里绘制
//下一个mousemove
lastX = mouseX;
lastY = mouseY;

//命令模式的东西:保存鼠标位置和
//刷子的大小/颜色到撤消数组
points.push({
x:mouseX,
y:mouseY,
size:brushSize,
color:brushColor,
mode:draw
}
}
}

,只是弹出绘图数组的最后一个点:

 函数undoLastPoint(){

//从绘图数组中删除最后一个绘制点
var lastPoint = points.pop();

//将undone点添加到单独的重做数组
redoStack.unshift(lastPoint);

//重绘所有剩余点
redrawAll();
}

重做在逻辑上更棘手。 >

最简单的重做是用户只能在撤消后立即重做。将每个撤消点保存在单独的重做数组中。



复杂的是,如果你让用户重做之后再重做,你可以将重做的位添加到主数组。他们做了更多的绘图。



例如,你最终可能会得到一条有两条尾巴的狗:一条新画的尾巴和一条第二条redo尾巴!



因此,如果在额外绘制后允许重做,则需要一种方法防止用户在重做期间混淆。 Matt Greer的分层重做的想法是一个好方法。只需通过保存重做点,而不是整个画布图像来改变这个想法。



这里是一个使用撤消数组的例子,我创建了一个前一个问题:绘画到油画中



这是代码和小提琴: http://jsfiddle.net/m1erickson/AEYYq/

 <!doctype html& 
< html>
< head>
< link rel =stylesheettype =text / cssmedia =allhref =css / reset.css/> <! - reset css - >
< script type =text / javascriptsrc =http://code.jquery.com/jquery.min.js>< / script>
<! - [if lt IE 9]>< script type =text / javascriptsrc =../ 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 point = [];


function handleMouseDown(e){
mouseX = parseInt(e.clientX-offsetX);
mouseY = parseInt(e.clientY- offsety);

//把你的mousedown东西放在这里
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,尺寸:brushSize,颜色:brushColor,模式:begin});
lastX = mouseX;
lastY = mouseY;
isMouseDown = true;
}

function handleMouseUp(e){
mouseX = parseInt(e.clientX-offsetX);
mouseY = parseInt(e.clientY-offsetY);

//把你的鼠标放在这里
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);

//把你的mousemove东西放在这里
if(isMouseDown){
ctx.lineTo(mouseX,mouseY);
ctx.stroke();
lastX = mouseX;
lastY = mouseY;
//命令模式填充
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();
}

函数undoLast(){
points.pop();
redrawAll();
}

ctx.lineJoin =round;
ctx.fillStyle = brushColor;
ctx.lineWidth = brushSize;

$(#brush5)。click(function(){brushSize = 5;});
$(#brush10)。click(function(){brushSize = 10;});
//重要!画笔颜色必须以6位十六进制格式定义
$(#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);});

//按住undo按钮删除最后一个线段
var interval;
$(#undo)。mousedown(function(){
interval = setInterval(undoLast,100);
})mouseup(function(){
clearInterval interval);
});


}); // end $(function(){});
< / script>

< / head>

< body>
< p>拖动即可绘制。使用按钮更改lineWidth / color< / p>
< canvas id =canvaswidth = 300 height = 300>< / canvas>< br>
< button id =undo>按住此按钮可撤消< / button>< br>< br>
< button id =brush5> 5px画笔< / button>
< button id =brush10> 10px刷< / button>
< button id =brushRed>红色画笔< / button>
< button id =brushBlue>蓝色画笔< / button>
< / body>
< / html>


I need to implement a undo/redo system for my paint program: http://www.taffatech.com/Paint.html

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?

解决方案

A word of warning!

Saving the whole canvas as an image for undo/redo is memory intensive and a performance killer.

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"
        });
    }
}

If the user wants to "undo", just pop the last point off the drawing array:

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();
}

Redo is logically more tricky.

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.

For example, you could end up with a dog with 2 tails: a newly drawn tail and a second "redo" tail !

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

Here is that code and a Fiddle: http://jsfiddle.net/m1erickson/AEYYq/

<!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>

这篇关于撤消/重做的油漆程序 - 画布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆