HTML5 Canvas - 为什么画布轴会受到转换的影响?

时间:2013-05-24 17:40:45

标签: html5 html5-canvas

我不了解HTML5 canvas系统。假设我有一个以(5,5)为中心的圆,我想将它缩放2,所以它的中心不会移动。好的,所以我的直觉说要做到以下几点:

  1. 通过translate(-5,-5)将圆圈移回(0,0)。
  2. 通过应用scale(2,2)将其缩放2。
  3. 通过应用translate(5, 5)将其移回(5,5)。
  4. 但它不起作用,因为画布的实现使得平移和缩放将应用于画布轴,而不是点本身(例如,translate只会移动原点,而不是圆的中心)。我是对的吗?

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

    我错过了什么吗?

    修改

    嗯,我想我错过的是转换按相反的顺序执行,如上所述here。但我仍然不明白为什么......为什么不按照原来的顺序?

3 个答案:

答案 0 :(得分: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 表示第一个翻译语句, S 表示语句, T2 表示第二个翻译语句,而 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     ]

答案 1 :(得分:2)

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

不对物体进行转换 转换是在坐标空间上完成的。

想想这一秒:
Canvas是一种即时模式绘图环境 一旦你把一个物体放下来,它会永久地停留在那里,就像一个巨大阵列中的数字一样。

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

因此,如果您正在绘制图片,并且想要缩放特定字符,则缩放坐标,位置并以该比例和原点绘制所有部分,然后按相反的顺序重置变换。

否则,会发生这种情况:

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

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

答案 2 :(得分:1)

实际上,你的直觉是正确的......

转换始终按订单收货完成;)

恭喜您访问whatWG来获取信息 - 没有多少人有足够的勇气去做!

whatWG所说的是,如果你执行一些变换(比如平移然后缩放)然后做一个绘图......然后回到你原来的未变换状态,你必须以相反的顺序撤销变换(unscale然后翻译)。

[附加插图]

这是取消转换的例证:

  1. 在[50,50]
  2. 处绘制蓝色参考点
  3. 做一些变换//翻译(100,100),缩放(2,2)
  4. 在变换坐标处绘制一个矩形
  5. 撤消变换//缩放(0.5,0.5),平移(-100,-100)
  6. 在[50,50]
  7. 处绘制一个红色参考点

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

    enter image description here

    任务完成!

    请注意,我们必须以相反的顺序撤消变换。

    同样, UNDO 的顺序相反。

    由于我们最初按比例缩小了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>