HTML画布 - 平移、旋转、裁剪 - 使用context.restore快速还原

4
几天前,我在询问有关动画速度的问题之后,stackoverflow群体再次解决了我的问题。然而,这又引发了另一个问题。[你知道的越多,你就会发现你不知道的越多。]
基本上,要让canvas运行得更快,状态变化越少越好。如果我只是改变fillStyle,那么使用ctx.save和ctx.restore是过度的,因为所有状态都会被恢复。过度=缓慢。相反,只需将旧的fillStyle值保留在某个地方,并在完成后将其放回即可。
那么如何针对ctx.translate(x,y),ctx.rotate(angle)和ctx.clip()做到这一点呢?
如何在不使用ctx.restore的情况下将这些状态恢复到更改之前的状态?
2个回答

4

您可以使用负值来取消转换。

ctx.translate(100,100);
// draw lots of stuff
ctx.translate(-100,-100);

ctx.scale(.75,.50);
// draw stuff
ctx.scale(-.75,-.50);

ctx.rotate(Math.PI/4);
// draw stuff
ctx.rotate(-Math.PI/4);

如果您进行了多个转换,则必须按相反的顺序撤消它们。
ctx.translate(100,100);
ctx.scale(.75,.50);    
ctx.rotate(Math.PI/4);

// draw lots of stuff

ctx.rotate(-Math.PI/4);
ctx.scale(-.75,-.50);
ctx.translate(-100,-100);

但是,如果只需要翻译(移动)几个项目,使用偏移量比使用变换更快。

strokeRect(20+100,20+100,50,30);
fillRect(20+100,20+100,50,30);

裁剪是半永久性的,因此您必须保存/恢复整个上下文状态以撤消裁剪:

context.save();
// define a clipping path
context.clip();
// draw stuff
context.restore();

使用变换矩阵进行变换。Canvas 可以通过 context.setTransform 方法让您访问该矩阵。

scaleX=.75;
scaleY=.50;
skewX=0;
skewY=0;
translateX=100;
translateY=100;

context.setTransform(scaleX, skewX, skewY, scaleY, translateX, translateY);

// draw stuff

context.setTransform(-scaleX, -skewX, -skewY, -scaleY, -translateX, -translateY);

如果您想设置旋转矩阵,您需要按以下方式设置比例和倾斜值的组合:

var radianAngle=Math.PI/4;
var cos=Math.cos(radianAngle);
var sin=Math.sin(radianAngle);

context.setTransform(cos,sin,-sin,-cos,0,0);

// draw stuff

context.setTransform(-cos,-sin,sin,cos,0,0);

要同时进行旋转和其他变换,只需将旋转值添加到缩放和倾斜值中。

context.setTransform(scaleX+cos, skewX+sin, skewY-sin, scaleY-cos, translateX, translateY);

// draw stuff

context.setTransform(-scaleX-cos, -skewX-sin, -skewY+sin, -scaleY+cos, -translateX, -translate);

@markE:你好,在上面的例子中,你写了 sin=Math.cos... - 这是正确的吗? - d13
@d13 不好意思,那是我的笔误。感谢你指出来...已经更正了! - markE
我认为这是错误的 - 你不能将线性变换(缩放和扭曲)与三角函数变换(旋转)结合起来。正确的代码是: - IVO GELOV
我认为这是错误的 - 在组合变换时,应该乘以单独的矩阵而不是简单地将相应单元格中的值相加。 - IVO GELOV

1

仅更正问题的假设:

错误:• 使用save()/restore()方法时,整个上下文状态都被保存/恢复。

让我们谦虚一点:30秒内想到的想法很可能会被主要浏览器的开发人员发现(并改进)。所以真相是:

正确:• 保存上下文几乎没有任何作用,恢复只适用于刚刚发生的事情。

如有疑问,您可以查看代码,但需要花费相当长的时间才能熟悉它(我使用webKit的canvas进行了确认)。 但是,查看有关此主题的各种jsperf要容易得多:它们显示手动保存/恢复一个或两个属性时的收益中等到小==>只有更改的内容才会被恢复。 当手动保存/恢复更多内容时,由于Javascript的开销,保存和恢复变得更快。 (http://jsperf.com/save-restore-vs-translate-twice/4)

另外一件事:谈论“过剩”的问题似乎非常夸张。不仅因为,如前所述,上下文的保存可能更快,而且因为“最佳”胜利是2倍,因此我们正自豪地花费2ns而不是4ns来进行保存。这必须与绘图所需的时间进行比较,并且可能真的不值得。
最后两件事: • 手动保存/恢复所引起的错误风险(“哎呀!我忘了在那个函数中恢复!”)。 • 可能会发生的舍入误差(scale(x,x)=>然后scale(1 / x,1 / x))
实际上,您可以无风险地节省时间: 1)批处理命令:只要可能(这完全取决于您的应用程序),批处理所有需要给定上下文状态的命令。 2)同样,您可以定义约定/规则,以防止保存/恢复上下文。例如:“在填充之前始终设置fillStyle”。这样,您永远不必担心当前的fillSyle。您在此处可以做的事情也非常取决于您的应用程序(以及是否使用外部API),但对于许多绘图可以节省大量时间。
所以我的建议是仅在明显简单的情况下使用手动保存/恢复(例如:您只更改globalAlpha),并使用约定/规则将上下文状态更改降至最低。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接