为绘画程序制作 'History Panel'
Making a 'History Panel' for a paint program
我正在使用 HTML5/Canvas 制作一个非常简单的 'pixel-painting' 程序。
我想为用户提供返回 'history' 的选项,就像 Photoshop / Adobe 程序中的历史记录面板一样。
基本上这将是一个撤消按钮,但您可以返回到操作的开始,并且还会有一个日志显示每个操作的详细信息。
这可能吗?我将如何开始存储这些数据?
Chrome 浏览器有多少可用内存才能在一个页面上显示? –(抱歉,如果这个问题很愚蠢,对 Javascript 来说还很陌生,并且在浏览器中工作。)
我已阅读 this undo button Question,这很相似,但我想让有关正在存储的数据的信息可见。
非常感谢您提供的任何帮助!
您可以在每次执行操作时将当前 canvas 复制到一个单独的。简单地显示旧 canvases 可以作为操作日志。
您可以直接向drawImage发送canvas:
destContext.drawImage( srcCanvas, 0, 0 );
如果该方法消耗太多内存,另一种方法是将所有命令存储在一个堆栈中,撤消时删除最后一个元素,然后从头开始重新绘制所有内容。
您需要构建一个简单的撤消-重做堆栈。然后您需要决定是否要存储矢量数据或图像数据。后者效率更高,但也会占用更多内存。在某些情况下,您可能想要存储两种类型的数据(图像顶部的路径)。
方法步骤简单:
- 存储新文档的初始状态。保持堆栈指针指向下一个空闲槽(例如使用数组)。
- 当按下鼠标时(或开始一些其他会导致更改的操作)向前移动堆栈指针。
- 松开鼠标按钮后,制作快照、创建缩略图等。将绘图存储为点还是位图由您决定。如果是位图数据,您可以通过使用例如 zip 压缩它来绕过存储 space。向前移动堆栈指针。如果此时堆栈中存在快照,请将其删除。
- 需要撤消时,只需退回之前存储的步骤并将堆栈指针移回即可。通过保留快照,您可以通过向前移动堆栈指针并重绘快照(如果有)来重做。
- 最后,为了可视化撤消-重做堆栈,您可以简单地将每个快照按比例渲染到单独的 canvas,并将其提取为您放入列表中的图像。
注意:创建撤消状态时,清除新堆栈指针位置之后的所有快照非常重要。这是因为如果已经使用了undo,没有变化就可以使用redo。但是,如果使用撤消并添加了新绘图,这将使接下来的状态无效,因此必须将它们删除。
至于浏览器内存,这将取决于用户的系统。有些有几千兆字节,有些有很多。没有办法知道。您必须选择适合您的场景和目标受众的用户体验策略。
例子
这没有实现处理缩略图同步的后勤工作,但有大部分其他部分。我会把剩下的留作练习。
var ctx = c.getContext("2d"),
stack = [], // undo-redo stack
sp = 0, // stack pointer
isDown = false; // for drawing (demo)
capture(); // create an initial undo capture (blank)
ctx.lineCap = "round"; // setup line for demo
ctx.lineWidth = 4;
// simple draw mechanism
c.onmousedown = function(e) {
sp++; // on mouse down, move stack pointer to next slot
isDown = true; // NOTE: clear any snapshots after this point (not shown)
var pos = getXY(e); // start drawing some line - how you draw is up to you
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmousemove = function(e) {
if (!isDown) return; // only for drawing
var pos = getXY(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmouseup = function() {
if (!isDown) return;
isDown = false;
capture(); // capture an undo state
makeThumb(); // create and insert a thumbnail of state
};
function capture() {
stack[sp] = c.toDataURL(); // one way, you could use getImageData,
// or store points instead.. it's up to you
}
// Creates a thumbnail of current canvas and insert into visible undo stack
function makeThumb() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 64;
var ctxTmp = canvas.getContext("2d");
ctxTmp.drawImage(c, 0, 0, canvas.width, canvas.height);
undos.appendChild(canvas);
}
// UNDO button clicked
undo.onclick = function() {
var img = new Image; // restore previous state/snapshot
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer back and get previous snapshot
if (sp > 0) img.src = stack[--sp];
};
// REDO button clicked
redo.onclick = function() {
// anything we can redo?
if (sp < stack.length) {
var img = new Image;
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer forward and get next snapshot
img.src = stack[++sp];
}
};
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
#c {background:#ccc}
<button id=undo>Undo</button>
<button id=redo>Redo</button><br>
<canvas id=c width=500 height=500></canvas>
<div id=undos></div>
我正在使用 HTML5/Canvas 制作一个非常简单的 'pixel-painting' 程序。 我想为用户提供返回 'history' 的选项,就像 Photoshop / Adobe 程序中的历史记录面板一样。
基本上这将是一个撤消按钮,但您可以返回到操作的开始,并且还会有一个日志显示每个操作的详细信息。
这可能吗?我将如何开始存储这些数据?
Chrome 浏览器有多少可用内存才能在一个页面上显示? –(抱歉,如果这个问题很愚蠢,对 Javascript 来说还很陌生,并且在浏览器中工作。)
我已阅读 this undo button Question,这很相似,但我想让有关正在存储的数据的信息可见。
非常感谢您提供的任何帮助!
您可以在每次执行操作时将当前 canvas 复制到一个单独的。简单地显示旧 canvases 可以作为操作日志。
您可以直接向drawImage发送canvas:
destContext.drawImage( srcCanvas, 0, 0 );
如果该方法消耗太多内存,另一种方法是将所有命令存储在一个堆栈中,撤消时删除最后一个元素,然后从头开始重新绘制所有内容。
您需要构建一个简单的撤消-重做堆栈。然后您需要决定是否要存储矢量数据或图像数据。后者效率更高,但也会占用更多内存。在某些情况下,您可能想要存储两种类型的数据(图像顶部的路径)。
方法步骤简单:
- 存储新文档的初始状态。保持堆栈指针指向下一个空闲槽(例如使用数组)。
- 当按下鼠标时(或开始一些其他会导致更改的操作)向前移动堆栈指针。
- 松开鼠标按钮后,制作快照、创建缩略图等。将绘图存储为点还是位图由您决定。如果是位图数据,您可以通过使用例如 zip 压缩它来绕过存储 space。向前移动堆栈指针。如果此时堆栈中存在快照,请将其删除。
- 需要撤消时,只需退回之前存储的步骤并将堆栈指针移回即可。通过保留快照,您可以通过向前移动堆栈指针并重绘快照(如果有)来重做。
- 最后,为了可视化撤消-重做堆栈,您可以简单地将每个快照按比例渲染到单独的 canvas,并将其提取为您放入列表中的图像。
注意:创建撤消状态时,清除新堆栈指针位置之后的所有快照非常重要。这是因为如果已经使用了undo,没有变化就可以使用redo。但是,如果使用撤消并添加了新绘图,这将使接下来的状态无效,因此必须将它们删除。
至于浏览器内存,这将取决于用户的系统。有些有几千兆字节,有些有很多。没有办法知道。您必须选择适合您的场景和目标受众的用户体验策略。
例子
这没有实现处理缩略图同步的后勤工作,但有大部分其他部分。我会把剩下的留作练习。
var ctx = c.getContext("2d"),
stack = [], // undo-redo stack
sp = 0, // stack pointer
isDown = false; // for drawing (demo)
capture(); // create an initial undo capture (blank)
ctx.lineCap = "round"; // setup line for demo
ctx.lineWidth = 4;
// simple draw mechanism
c.onmousedown = function(e) {
sp++; // on mouse down, move stack pointer to next slot
isDown = true; // NOTE: clear any snapshots after this point (not shown)
var pos = getXY(e); // start drawing some line - how you draw is up to you
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmousemove = function(e) {
if (!isDown) return; // only for drawing
var pos = getXY(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmouseup = function() {
if (!isDown) return;
isDown = false;
capture(); // capture an undo state
makeThumb(); // create and insert a thumbnail of state
};
function capture() {
stack[sp] = c.toDataURL(); // one way, you could use getImageData,
// or store points instead.. it's up to you
}
// Creates a thumbnail of current canvas and insert into visible undo stack
function makeThumb() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 64;
var ctxTmp = canvas.getContext("2d");
ctxTmp.drawImage(c, 0, 0, canvas.width, canvas.height);
undos.appendChild(canvas);
}
// UNDO button clicked
undo.onclick = function() {
var img = new Image; // restore previous state/snapshot
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer back and get previous snapshot
if (sp > 0) img.src = stack[--sp];
};
// REDO button clicked
redo.onclick = function() {
// anything we can redo?
if (sp < stack.length) {
var img = new Image;
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer forward and get next snapshot
img.src = stack[++sp];
}
};
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
#c {background:#ccc}
<button id=undo>Undo</button>
<button id=redo>Redo</button><br>
<canvas id=c width=500 height=500></canvas>
<div id=undos></div>