关于 svg 中矩阵值 e 和 f 的误解

Misconception regarding the matrix values e and f in svg

关于 svg 元素的 matrix,特别是 ef 部分,我似乎有一个逻辑上的误解,希望有人能帮我弄清楚。

基于this

Translation is equivalent to the matrix:

or [1 0 0 1 tx ty], where tx and ty are the distances to translate coordinates in x and y, respectively.

例子

下面提供两个小例子svg。每个包含一个 path,它们都被旋转 15°。我试图用石灰 circle.

标记点 p(x1,y1)

;window.onload = () => {
  //REM: Query the first element in svg => [path, path]
  document.querySelectorAll('svg > *[transform]').forEach((element) => {
    let tSVG = element.ownerSVGElement;
    let tBBox = element.getBBox();
    let tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Draw a circle at p(x1,y1) - before rotation
    let tOldX = tBBox.x * tMatrix.a + tMatrix.e;
    let tOldY = tBBox.y * tMatrix.d + tMatrix.f

    let tCircle1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle1.setAttributeNS(null, 'r', '5');
    tCircle1.setAttributeNS(null, 'fill', 'orange');
    tCircle1.setAttributeNS(null, 'fill-opacity', '0.5');
    tCircle1.setAttributeNS(null, 'cx', tOldX);
    tCircle1.setAttributeNS(null, 'cy', tOldY);
    tSVG.appendChild(tCircle1);

    //REM: Rotate by 15° around center of itself
    let tTransform = tSVG.createSVGTransform();
    tTransform.setRotate(15, tBBox.x + tBBox.width / 2, tBBox.y + tBBox.height / 2);
    element.transform.baseVal.appendItem(tTransform);
    element.transform.baseVal.initialize(element.transform.baseVal.consolidate());

    //REM: Fetch new matrix
    tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Pure scale
    let tScaleX = Math.sqrt(tMatrix.a * tMatrix.a + tMatrix.b * tMatrix.b);
    let tScaleY = Math.sqrt(tMatrix.c * tMatrix.c + tMatrix.d * tMatrix.d);

    //REM: Draw a circle at p(x1,y1) - after rotation
    let tNewX = tBBox.x * tScaleX + tMatrix.e;
    let tNewY = tBBox.y * tScaleY + tMatrix.f;

    let tCircle2 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle2.setAttributeNS(null, 'r', '5');
    tCircle2.setAttributeNS(null, 'fill', 'lime');
    tCircle2.setAttributeNS(null, 'fill-opacity', '0.9');
    tCircle2.setAttributeNS(null, 'cx', tNewX);
    tCircle2.setAttributeNS(null, 'cy', tNewY);
    tSVG.appendChild(tCircle2);

    //REM: Connection
    let tLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
    tLine.setAttributeNS(null, 'stroke', 'green');
    tLine.setAttributeNS(null, 'x1', tOldX);
    tLine.setAttributeNS(null, 'y1', tOldY);
    tLine.setAttributeNS(null, 'x2', tNewX);
    tLine.setAttributeNS(null, 'y2', tNewY);
    tSVG.appendChild(tLine);

    console.log(
      'e: ' + tMatrix.e,
      'f: ' + tMatrix.f
    )
  })
}
<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '950,-250,100,400' height='800' width='300'>
  <path d = 'm1029.31 112.6h97.40v57.01h-97.40z' fill = 'red' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)' />
</svg>

<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '-40,-40,150,100' height='200' width='300'>
  <path d = 'M0 0L100 0L100 60L0 60z' fill = 'blue' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)'></path>
</svg>

问题

根据我的理解,我应该能够通过使用以下计算:

然而,它只适用于 second/blue svg 而不是 first/red svg。 first/red svg 的 f 对我来说似乎有点离谱:

我哪里出错了?

备注

虽然我为语法添加了 Javascript 标记,但重点不在 Javascript 上。 Javascript 提供了正确放置石灰 circle 的其他可能性。

我没有研究它失败的确切原因。我有几个想法。但是,如果您应用正确的矩阵乘法公式,它就可以正常工作。

let tNewX = tBBox.x * tMatrix.a + tBBox.y * tMatrix.c + tMatrix.e;
let tNewY = tBBox.x * tMatrix.b + tBBox.y * tMatrix.d + tMatrix.f;

;window.onload = () => {
  //REM: Query the first element in svg => [path, path]
  document.querySelectorAll('svg > *[transform]').forEach((element) => {
    let tSVG = element.ownerSVGElement;
    let tBBox = element.getBBox();
    let tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Draw a circle at p(x1,y1) - before rotation
    let tOldX = tBBox.x * tMatrix.a + tMatrix.e;
    let tOldY = tBBox.y * tMatrix.d + tMatrix.f

    let tCircle1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle1.setAttributeNS(null, 'r', '5');
    tCircle1.setAttributeNS(null, 'fill', 'orange');
    tCircle1.setAttributeNS(null, 'fill-opacity', '0.5');
    tCircle1.setAttributeNS(null, 'cx', tOldX);
    tCircle1.setAttributeNS(null, 'cy', tOldY);
    tSVG.appendChild(tCircle1);

    //REM: Rotate by 15° around center of itself
    let tTransform = tSVG.createSVGTransform();
    tTransform.setRotate(15, tBBox.x + tBBox.width / 2, tBBox.y + tBBox.height / 2);
    element.transform.baseVal.appendItem(tTransform);
    element.transform.baseVal.initialize(element.transform.baseVal.consolidate());

    //REM: Fetch new matrix
    tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Draw a circle at p(x1,y1) - after rotation
    let tNewX = tBBox.x * tMatrix.a + tBBox.y * tMatrix.c + tMatrix.e;
    let tNewY = tBBox.x * tMatrix.b + tBBox.y * tMatrix.d + tMatrix.f;

    let tCircle2 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle2.setAttributeNS(null, 'r', '5');
    tCircle2.setAttributeNS(null, 'fill', 'lime');
    tCircle2.setAttributeNS(null, 'fill-opacity', '0.9');
    tCircle2.setAttributeNS(null, 'cx', tNewX);
    tCircle2.setAttributeNS(null, 'cy', tNewY);
    tSVG.appendChild(tCircle2);

    //REM: Connection
    let tLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
    tLine.setAttributeNS(null, 'stroke', 'green');
    tLine.setAttributeNS(null, 'x1', tOldX);
    tLine.setAttributeNS(null, 'y1', tOldY);
    tLine.setAttributeNS(null, 'x2', tNewX);
    tLine.setAttributeNS(null, 'y2', tNewY);
    tSVG.appendChild(tLine);

    console.log(
      'e: ' + tMatrix.e,
      'f: ' + tMatrix.f
    )
  })
}
<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '950,-250,100,400' height='800' width='300'>
  <path d = 'm1029.31 112.6h97.40v57.01h-97.40z' fill = 'red' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)' />
</svg>

<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '-40,-40,150,100' height='200' width='300'>
  <path d = 'M0 0L100 0L100 60L0 60z' fill = 'blue' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)'></path>
</svg>