0

关于 HTML5 画布系统,我有一些不明白的地方。假设我有一个以(5,5)为中心的圆,我想将它缩放 2,所以它的中心不会移动。好的,所以我的直觉说要执行以下操作:

  1. 将圆移回 (0,0) 处translate(-5,-5)
  2. 通过应用将其缩放 2 倍scale(2,2)
  3. 通过应用将其移回 (5,5) translate(5, 5)

但它不起作用,因为画布被实现为平移和缩放将应用于画布轴,而不是点本身(例如,平移只会移动原点,而不是圆的中心)。我对吗?

但这是为什么呢?为什么转换不直接应用于点,所以一切都将始终相对于 (0,0) ?这不是实施此类系统的标准方法吗?

我错过了什么吗?

编辑:

好吧,我想我错过的是转换以相反的顺序执行,如此所述。但我还是不明白为什么……为什么不按原来的顺序?

4

3 回答 3

3

正如您所相信的那样,转换并不适用于画布轴,而是实际适用于点。HTML5 2D 渲染上下文的转换由转换矩阵控制。当前绘制状态的变换矩阵在创建当前默认路径以及绘制形状、图像和Path对象时应用。

这是转换矩阵的样子:

[a c tx]
[b d ty]
[0 0  1]

在哪里,

a - x 比例, c - x 倾斜, tx - x 平移

b - y 倾斜, d - y 比例, ty - y 平移

假设您尝试在该点绘制一个像素(x, y)。以下是如何将转换应用于它:

[sx]   [a c tx] [x]
[sy] = [b d ty]*[y]
[ 1]   [0 0  1] [1]

=>
[sx]   [a*x + c*y + tx]
[sy] = [b*x + d*y + ty]
[ 1]   [ 0  +  0  +  1]

(sx, sy)是实际绘制像素的输出位图上的坐标。对于单位矩阵 ( a = 1, c = 0, tx = 0, b = 0, d = 1, ty = 0),(sx, sy)将与 相同(x, y),这是默认情况。

现在,画布的变换矩阵使用列向量作为坐标这一事实为我们提供了变换似乎以相反顺序应用的原因。

让我们举个例子。您可以使用以下语句更改转换矩阵:

translate(-5, -5);
scale(2, 2);
translate(5, 5);

让我们来看看这些语句如何影响转换矩阵:

//default
[1 0 0]
[0 1 0]
[0 0 1]

->translate(-5, -5)
[1 0 0]   [1 0 -5]
[0 1 0] * [0 1 -5]
[0 0 1]   [0 0  1]
=>
[1 0 -5]
[0 1 -5]           //Using MI = IM = M, I is Identity Matrix
[0 0  1]

->scale(2, 2)
[1 0 -5]   [2 0 0]
[0 1 -5] * [0 2 0]
[0 0  1]   [0 0 1]
=>
[(1*2 + 0*0 + -5*0) (1*0 + 0*2 + -5*0) (1*0 + 0*0 + -5*1)]
[(0*2 + 1*0 + -5*0) (0*0 + 1*2 + -5*0) (0*0 + 1*0 + -5*1)]
[(0*2 + 0*0 +  1*0) (0*0 + 0*2 +  1*0) (0*0 + 0*0 +  1*1)]
=>
[2 0 -5]
[0 2 -5]
[0 0  1]

->translate(5, 5)
[2 0 -5]   [1 0 5]
[0 2 -5] * [0 1 5]
[0 0  1]   [0 0 1]
=>
[(2*1 + 0*0 + -5*0) (2*0 + 0*1 + -5*0) (2*5 + 0*5 + -5*1)]
[(0*1 + 2*0 + -5*0) (0*0 + 2*1 + -5*0) (0*5 + 2*5 + -5*1)]
[(0*1 + 0*0 +  1*0) (0*0 + 0*1 +  1*0) (0*5 + 0*5 +  1*1)]
=>
[2 0 5]
[0 2 5] 
[0 0 1]

这意味着,对于一个点(x, y),对应(sx, sy)的是

[sx]   [2*x + 5]   [2*x + 0*y + 5] 
[sy] = [2*y + 5] = [0*x + 2*y + 5] 
[ 1]   [   1   ]   [0*x + 0*y + 1]

因此,如果(5, 5)是您不希望缩放影响的中心,则使用当前代码将在(15, 15).

请注意,我首先将所有三个矩阵相乘以创建新的转换矩阵,这可能无法很清楚为什么转换似乎是反向应用的。但是,如果T1表示第一个 translate 语句,S表示 scale 语句,T2表示第二个 translate 语句,而X表示要转换的点的列向量,很容易看出

T = I*T1*S*T2 
=>
T = T1*S*T2

那么,如果C表示 的列向量(sx, sy)

C = T*X
=>
C = T1*S*T2*X
=>
C = T1*S*X'      //X' is X transformed by T2
=>
C = T1*X''       //X'' is X' transformed by S
=>
C = X'''         //X''' is X'' transformed by T1

or
    [(x+5)*2 - 5]
C = [(y+5)*2 - 5] = X''' 
    [     1     ]
于 2013-05-25T05:25:54.910 回答
2

这就是发挥作用.save的地方。.restore

不会对对象进行转换。
变换是在坐标空间上完成的。

想一想:
Canvas 是一个即时模式的绘图上下文。
一旦你放下一个物体,它就会永远卡在那里,就像一个巨大的数组中的数字一样。

所以你不能制作一个正方形然后渲染它然后重新缩放它并移动它等等,除非你自己在你自己制作的场景图中跟踪虚拟“对象”,你正在做你的根据需要自行擦除和更新光栅图像。

因此,如果您正在绘制图片,并且想要缩放特定角色,您可以缩放坐标、定位并以该比例和原点绘制所有作品,然后以相反的顺序重置您的变换。

否则,会发生这种情况:

scale(2) => translate(5,5) =>
scale(0.5) => translate(-5, -5)

向右移动 10,向下移动 10,然后向左移动 5,向上移动 5。

于 2013-05-24T19:43:21.160 回答
1

其实你的直觉是对的……

转换总是按收到的顺序完成;)

恭喜您前往 whatWG 来源获取信息——没有多少人有足够的勇气这样做!

whatWG 的意思是,如果您执行一些变换(例如先平移然后缩放)然后进行绘图...然后要回到原始未变换状态,您必须以相反的顺序撤消变换(先缩放然后未平移) .

[补充说明]

这是未转换的示例:

  1. 在 [50,50] 处绘制一个蓝色参考点
  2. 做一些变换 // translate(100,100), scale(2,2)
  3. 在变换后的坐标处绘制一个矩形
  4. 撤消变换 // scale(0.5, 0.5), translate(-100,-100)
  5. 在 [50,50] 处绘制一个红色参考点

只有当我们完全未翻译时,这些点才会对齐!

在此处输入图像描述

任务完成!

请注意,我们必须以与其创建相反的顺序撤消转换。

同样,以相反的顺序撤消

由于我们最初按 2 倍放大,因此我们需要按 0.5 缩小。

由于我们最初翻译(移动)了 100,100,我们需要取消翻译 -100,-100

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

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

<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    drawReferenceDot(15,"blue");

    ctx.beginPath();
    // transform
    ctx.translate(100,100);
    ctx.scale(2,2);

    // draw
    ctx.rect(0,0,25,25);
    ctx.fill();

    // un-transform (in exactly reverse order)
    // first un-scale -- using 0.5,0.5
    // then un-transform -- using -100,-100
    ctx.scale(0.5,0.5);
    ctx.translate(-100,-100);

    // draw a second reference dot
    // if we've correctly "untransformed" the dots should align
    drawReferenceDot(8,"red");


    function drawReferenceDot(radius,color){
        ctx.beginPath();
        ctx.fillStyle=color;
        ctx.arc(50,50,radius,0,Math.PI*2,false);
        ctx.fill();
    }

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

</head>

<body>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
于 2013-05-24T18:46:10.617 回答