撤消/重做绘画程序 – canvas

我需要为我的绘图程序实现一个撤消/重做系统: http : //www.taffatech.com/Paint.html

我想出的想法是有2个arrays堆栈,一个用于撤销,一个用于重做。 无论何时绘制和释放鼠标,它都会通过推送将canvas图像保存到撤消数组堆栈。 如果你绘制其他东西并释放它也会做同样的事情。 但是,如果单击“撤消”,它将弹出撤消数组的顶部图像并将其打印到canvas,然后将其推送到重做堆栈。

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

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

一句警告!

将整个canvas保存为撤消/重做的图像是内存密集型和性能杀手。

但是,您逐步将用户的绘图保存在数组中的想法仍然是一个好主意。

不是将整个canvas保存为图像,而是创建一个点数组来记录用户在绘制时所做的每个鼠标移动。 这是您的“绘图数组”,可用于完全重绘您的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(); } 

重做在逻辑上更棘手。

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

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

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

因此,如果您在额外绘制后允许重做,则需要一种方法来防止用户在重做期间感到困惑。 Matt Greer关于“分层”重做的想法是一种好方法。 只需通过保存重做点来改变这个想法,而不是整个canvas图像。 然后用户可以打开/关闭重做以查看他们是否想要重做。

下面是一个使用我为前一个问题创建的撤消数组的示例:在绘画中绘制到canvas

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

            

Drag to draw. Use buttons to change lineWidth/color




这是我为我的绘画应用程序所做的基本想法; 并且它确实运行良好,但这种方法可能非常耗费内存。

所以我做的一点点调整只是存储撤消/重做剪辑,其大小与用户执行的最后一个操作相同。 因此,如果他们只是画出一小块canvas,你可以存储一个小尺寸的canvas,并且可以节省大量内存。

我的undo / redo系统存在于Painter.js中 。 我两年前写过这个应用程序,所以我的记忆有点模糊,但如果你决定解码我所做的事情,我可以帮忙解释一下。

尝试实现Command设计模式 。

这里还有另一个类似的问题: “撤消”function的最佳设计模式