如何从旋转/平移/缩放值计算SVG变换矩阵?

40
我手上有以下细节:
<g transform="translate(20, 50) scale(1, 1) rotate(-30 10 25)">

需要将上面的行更改为:
<g transform="matrix(?,?,?,?,?,?)">

有人能帮我实现这个吗?

3个回答

78
translate(tx, ty) 可以写成矩阵形式:
1  0  tx
0  1  ty
0  0  1

scale(sx, sy)可以写成矩阵形式:
sx  0   0
0   sy  0
0   0   1

rotate(a)可以写成矩阵形式:
cos(a)  -sin(a)  0
sin(a)   cos(a)  0
0        0       1

rotate(a, cx, cy) 是通过 (cx, cy) 进行平移,旋转 a 度,然后再通过 (-cx, -cy) 进行平移的组合操作(source)。将这些变换的矩阵相乘得到以下结果:
cos(a)  -sin(a)  -cx × cos(a) + cy × sin(a) + cx
sin(a)   cos(a)  -cx × sin(a) - cy × cos(a) + cy
0        0        1

如果你将translate(tx, ty)矩阵与rotate(a, cx, cy)矩阵相乘,你会得到:
cos(a)  -sin(a)  -cx × cos(a) + cy × sin(a) + cx + tx
sin(a)   cos(a)  -cx × sin(a) - cy × cos(a) + cy + ty
0        0        1

对应于SVG变换矩阵的是:
(cos(a), sin(a), -sin(a), cos(a), -cx × cos(a) + cy × sin(a) + cx + tx, -cx × sin(a) - cy × cos(a) + cy + ty)。
在您的情况下是:matrix(0.866, -0.5 0.5 0.866 8.84 58.35)。
如果您包括缩放(sx, sy)变换,矩阵为:
(sx × cos(a), sy × sin(a), -sx × sin(a), sy × cos(a), (-cx × cos(a) + cy × sin(a) + cx) × sx + tx, (-cx × sin(a) - cy × cos(a) + cy) × sy + ty)
请注意,这假设您按照您编写的顺序进行变换。

你能否详细说明一下a、cx、cy、tx和ty是什么?这样我才能尝试一下。 - Vinay Bagale
在你的例子中,a=-30度,cx=10,cy=25,tx=20,ty=50(sx=1,sy=1)。通常旋转由rotate(a, cx, cy)给出,平移由translate(tx, ty)给出。 - Peter Collingridge
@reabow - 回复:所以cx和cy是偏移量,而不是坐标...? cx和cy是旋转围绕的中心坐标。它不一定在被旋转的图形内部。 - Jesse Chisholm
@thednp 你想做什么?这将取决于扭曲的顺序。如果您想在保持所有变量的同时组合多个转换,那么情况会变得混乱。 - Peter Collingridge
@PeterCollingridge 是的,我决定放弃深入研究这个问题了,常规转换已经足够了,matrix 对我来说仍然是一个难以处理的问题。 - thednp
显示剩余6条评论

13

首先,如果g元素有id属性或其他适当的方法,请使用document.getElementById获取该元素,然后调用consolidate函数。

var g = document.getElementById("<whatever the id is>");
g.transform.baseVal.consolidate();

1
我正在尝试使用Objective C/iOS实现这个。有没有通用的方法来获取矩阵? - Vinay Bagale
尽管彼得的回答解释了理论以及如何手动计算矩阵,但我认为罗伯特的回答应该被接受。el.transform.baseVal.consolidate()是你应该使用的方法。没有必要重新发明轮子。 - Manfred
@Manfred,根据OP在我的答案下面的评论所述,他无法按照说明使用它,那么他为什么要接受呢? - Robert Longson
说实话,我没看他的评论,但是 OP 的问题中没有提到 Objective C/iOS。 我遇到了与 OP 问题中描述的相同的问题,差点接受了被采纳的答案,但实际上我真正需要的是本地的 JavaScript 函数consolidate()。 - Manfred
@Manfred,这就是现实啊 ;-),提问者在撰写问题时通常没有明确设置他们正在操作的约束条件。不过很高兴我的回答对你有所帮助。 - Robert Longson
这似乎不起作用,如果通过CSS样式属性设置了转换,这是Chrome现在的做法。 - john k

3

可能有帮助:

  1. 实时演示如何查找转换点的实际坐标

  2. 接受答案的实现:

    function multiplyMatrices(matrixA, matrixB) {
        let aNumRows = matrixA.length;
        let aNumCols = matrixA[0].length;
        let bNumRows = matrixB.length;
        let bNumCols = matrixB[0].length;
        let newMatrix = new Array(aNumRows);
    
        for (let r = 0; r < aNumRows; ++r) {
            newMatrix[r] = new Array(bNumCols);
    
            for (let c = 0; c < bNumCols; ++c) {
                newMatrix[r][c] = 0;
    
                for (let i = 0; i < aNumCols; ++i) {
                    newMatrix[r][c] += matrixA[r][i] * matrixB[i][c];
                }
            }
        }
    
        return newMatrix;
    }
    
    let translation = {
        x: 200,
        y: 50
    };
    let scaling = {
        x: 1.5,
        y: 1.5
    };
    let angleInDegrees = 25;
    let angleInRadians = angleInDegrees * (Math.PI / 180);
    let translationMatrix = [
        [1, 0, translation.x],
        [0, 1, translation.y],
        [0, 0, 1],
    ];
    let scalingMatrix = [
        [scaling.x, 0, 0],
        [0, scaling.y, 0],
        [0, 0, 1],
    ];
    let rotationMatrix = [
        [Math.cos(angleInRadians), -Math.sin(angleInRadians), 0],
        [Math.sin(angleInRadians), Math.cos(angleInRadians), 0],
        [0, 0, 1],
    ];
    let transformMatrix = multiplyMatrices(multiplyMatrices(translationMatrix, scalingMatrix), rotationMatrix);
    
    console.log(`matrix(${transformMatrix[0][0]}, ${transformMatrix[1][0]}, ${transformMatrix[0][1]}, ${transformMatrix[1][1]}, ${transformMatrix[0][2]}, ${transformMatrix[1][2]})`);
    

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