2013-06-17 2 views
4

나는 나의 페인트 프로그램에 대한 실행 취소/재실행 시스템을 구현해야합니다 http://www.taffatech.com/Paint.html실행 취소/다시 캔버스

내가이 배열 스택에 대한 실행 취소 하나 하나를 가지고있다 내놓았다 내 생각 다시 하다. 마우스를 끌어서 놓을 때마다 캔버스 이미지를 푸시하여 배열 실행 취소 스택에 저장합니다. 당신이 뭔가 다른 것을 그리고 그걸 놓으면 똑같이 할 것입니다. 그러나 실행 취소를 클릭하면 실행 취소 배열의 맨 위 이미지가 팝업되어 캔버스에 인쇄 된 다음 다시 실행 스택으로 푸시됩니다.

클릭하면 다시 실행되고 실행 취소하려면이 버튼을 누릅니다. 실행 취소의 상단은 각 마우스를 끈 후에 인쇄됩니다.

올바른 방법인가요? 더 좋은 방법이 있습니까?

+0

당신은 [fabric.js]로 시도해 볼 수도를 (여기

내가 이전 질문에 대해 작성된 실행 취소 배열을 사용하는 예입니다 http://fabricjs.com/)을 사용하면 자유롭게 그림을 그릴 수 있으며 각 도형을 객체로 감쌀 수 있습니다 ([here] (http://fabricjs.com/fabric-intro-part-4/) 참조). 수행 할 작업 – Jacopofar

+1

실행 취소 스택에 새로운 작업이 저장 될 때 다시 실행 스택을 지우는 것을 잊지 마십시오. – Bergi

+1

전체 이미지를 저장하는 데 메모리가 많이 소모 될 수 있습니다. 스택 크기를 조정하거나 이미지간에 변경 사항을 저장해보십시오 (기본적으로 모든 획). –

답변

10

경고 단어!

실행 취소/다시 실행을위한 이미지로 전체 캔버스를 저장하는 것은 메모리 집약적이며 성능 저하 요인입니다.

그러나 배열에서 사용자의 그림을 점차적으로 저장한다는 생각은 여전히 ​​좋습니다.

전체 캔버스를 이미지로 저장하는 대신 사용자가 그리는 모든 mousemove를 기록하는 점 배열을 만들면됩니다. 이것은 캔버스를 완전히 다시 그리는 데 사용할 수있는 "그리기 배열"입니다.

사용자가 마우스를 끌 때마다 폴리 라인 (연결된 선분 그룹의 그룹)이 생성됩니다. 사용자가 드래그하여 선을 만들면 해당 mousemove 점을 드로잉 배열에 저장하고 폴리 라인을 현재 mousemove 위치로 확장합니다.

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

다시 실행은 논리적으로 더 까다 롭습니다.

가장 간단한 재실행은 사용자가 실행 취소 후에 즉시 다시 실행할 수있는 경우입니다. 각 "실행 취소"지점을 별도의 "재실행"배열에 저장하십시오. 그런 다음 다시 실행하려는 경우 다시 배열 비트를 다시 기본 배열에 추가하면됩니다.

더 많은 그림을 그린 후에 사용자가 "다시 실행"하도록하면 복잡합니다.

예를 들어 새로 꼬리와 두 번째 "다시 실행"꼬리가있는 꼬리가 2 개있는 강아지로 끝날 수 있습니다.

그래서 다시 그리기 후에 다시 실행을 허용하면 재실행 중에 사용자가 혼동을 피할 수있는 방법이 필요합니다. 매트 그리어 (Matt Greer)의 재 설계 계층화 아이디어는 좋은 방법입니다. 전체 캔버스 이미지가 아니라 재실행 점을 저장하여 아이디어를 변경하십시오. 그런 다음 사용자는 다시 실행을 유지할지 여부를 확인하기 위해 다시 실행을 설정/해제 할 수 있습니다. 그 코드와 바이올린 여기 Drawing to canvas like in paint

됩니다 : 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> 
+1

필자도이 방법을 고려했지만 채우기 버킷과 같은 도구를 추가 할 때 IMO가 확장되지 않습니다. 이 페이지에서 언급 한 시스템 중 어느 것도 잘 확장되지는 않지만, 도구의 실행 취소/다시 실행 (예 : 레이어 지우기 취소)을 넘어 서면 시스템이 크게 고장납니다. 나는 어려운 길에서 그것을 발견했다 :) –

+2

@MattGreer : 안녕하세요 ... 만나서 반가워요! 채우기 버킷의 개념을 추가하면 배율에 대해 말하는 것을 볼 수 있습니다. 그러나이 시나리오에서는 메타 명령을 도입 할 수 있습니다. 예를 들어, 버킷으로 채워진 모든 픽셀을 기록하는 대신, "채우기"라는 단어와 채워지는 경로에 대한 참조를 기록하면됩니다. "채우기"명령을 재생하면 지정된 경로에서 범람 채우기 기능이 실행됩니다. 확장 성 유지! 이것은 제가 검사관지도의 불완전 함을 허용 한 프로젝트에서 효과가있었습니다. – markE

0

Command design pattern을 구현해보십시오.

여기에 다른 비슷한 질문 있습니다 : 내 paint app를 위해 한 일의 기본적인 생각이다 Best design pattern for "undo" feature

+0

http://www.yankov.us/canvasundo/ 나는 이것을 발견했다. 그러나 doesnt는 나의 코드와 함께 일하는 것처럼 보인다. 생각할만한 이유가 있니? –

5

가; 이 방법이 매우 메모리 집약적 일 수 있다는 점을 제외하면 잘 작동합니다.

그래서 내가 한 작업은 사용자가 마지막으로 수행 한 작업의 크기 인 실행 취소/다시 실행 만 저장하는 것입니다. 따라서 캔버스의 작은 뭉치를 그리면 전체 크기의 일부인 작은 캔버스를 저장할 수 있고 많은 메모리를 절약 할 수 있습니다.

내 실행 취소/다시 실행 시스템이 Painter.js에 있습니다. 나는 2 년 전에이 앱을 썼다. 그래서 내 기억이 약간 흐릿하지만 내가 한 일을 해독하기로 결정했다면 나는 설명 할 수있다.

+1

+1 나는 겹겹이 쌓기를 좋아합니다 ... 사용자가 정리를 유지할 수있는 좋은 방법입니다. – markE