Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extract position, rotation and scale from matrix SVG

I have a simple problem: I want to extract translation (tx, ty), rotation (r) and scale (sx, sy) values form a transform matrix applied to my svg element.

Let's use this example:

<g
  id="myElement"
  transform="matrix(0.93893241,0.34410162,-0.34410162,0.93893241,363.88475,-76.125919)"
  >... </g>

If, in javascript I do

document.getElementById("myElement").getCTM()

I can access to a, b, c, d, e, f values. How can I get tx, ty, sx, sy and r from there? Thanks

like image 337
lviggiani Avatar asked May 03 '13 12:05

lviggiani


2 Answers

Inspired by this ActionScript version: https://gist.github.com/fwextensions/2052247, I wrote a JavaScript port:

    function deltaTransformPoint(matrix, point)  {

        var dx = point.x * matrix.a + point.y * matrix.c + 0;
        var dy = point.x * matrix.b + point.y * matrix.d + 0;
        return { x: dx, y: dy };
    }


    function decomposeMatrix(matrix) {

        // @see https://gist.github.com/2052247

        // calculate delta transform point
        var px = deltaTransformPoint(matrix, { x: 0, y: 1 });
        var py = deltaTransformPoint(matrix, { x: 1, y: 0 });

        // calculate skew
        var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
        var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));

        return {

            translateX: matrix.e,
            translateY: matrix.f,
            scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
            scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
            skewX: skewX,
            skewY: skewY,
            rotation: skewX // rotation is the same as skew x
        };        
    }

Usage: decomposeMatrix(document.getElementById('myElement').getCTM())

like image 171
dave Avatar answered Oct 19 '22 14:10

dave


To decompose separately rotation and the skew you can check those examples:

react-native:

https://github.com/facebook/react-native/blob/master/Libraries/Utilities/MatrixMath.js

three.js: https://github.com/mrdoob/three.js/blob/302f1d155835888a77aad31241e0d74a2ee2f926/src/math/Matrix4.js

https://github.com/ismailman/decompose-dommatrix/blob/master/decomposeDommatrix.mjs

http://math.stackexchange.com/questions/861674/decompose-a-2d-arbitrary-transform-into-only-scaling-and-rotation

https://gist.github.com/Breton/9d217e0375de055d563b9a0b758d4ae6

function decomposeMatrix(m) {
  var E = (m.a + m.d) / 2
  var F = (m.a - m.d) / 2
  var G = (m.c + m.b) / 2
  var H = (m.c - m.b) / 2

  var Q = Math.sqrt(E * E + H * H);
  var R = Math.sqrt(F * F + G * G);
  var a1 = Math.atan2(G, F);
  var a2 = Math.atan2(H, E);
  var theta = (a2 - a1) / 2;
  var phi = (a2 + a1) / 2;

  // The requested parameters are then theta, 
  // sx, sy, phi,
  return {
    translateX: m.e,
    translateY: m.f,
    rotate: -phi * 180 / Math.PI,
    scaleX: Q + R,
    scaleY: Q - R,
    skew: -theta * 180 / Math.PI
  };
}

https://www.w3.org/TR/css-transforms-1/#decomposing-a-2d-matrix

  function decomposeMatrix2DW3(m) {

      var row0x = m.a;
      var row0y = m.b;
      var row1x = m.c;
      var row1y = m.d;

      var scaleX = Math.sqrt(row0x * row0x + row0y * row0y)
      var scaleY = Math.sqrt(row1x * row1x + row1y * row1y)

      // If determinant is negative, one axis was flipped.
      var determinant = row0x * row1y - row0y * row1x
      if (determinant < 0)
        // Flip axis with minimum unit vector dot product.
        if (row0x < row1y)
          scaleX = -scaleX
        else
          scaleY = -scaleY

      // Renormalize matrix to remove scale.
      if (scaleX) {
        row0x *= 1 / scaleX
        row0y *= 1 / scaleX
      }

      if (scaleY) {
        row1x *= 1 / scaleY
        row1y *= 1 / scaleY
      }

      // Compute rotation and renormalize matrix.
      var angle = Math.atan2(row0y, row0x);

      if (angle) {
        // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)]
        //                = [row0x, -row0y, row0y, row0x]
        // Thanks to the normalization above.
        var sn = -row0y
        var cs = row0x
        var m11 = row0x
        var m12 = row0y
        var m21 = row1x
        var m22 = row1y
        row0x = cs * m11 + sn * m21
        row0y = cs * m12 + sn * m22
        row1x = -sn * m11 + cs * m21
        row1y = -sn * m12 + cs * m22
      }

      m11 = row0x
      m12 = row0y
      m21 = row1x
      m22 = row1y

      // Convert into degrees because our rotation functions expect it.
      angle = angle * (180 / Math.PI);
      // The requested parameters are then theta, 
      // sx, sy, phi,
      return {
        translateX: m.e,
        translateY: m.f,
        rotateZ: angle,
        scaleX: scaleX,
        scaleY: scaleY,
        matrix: [m11, m12, m21, m22, 0, 0]
      };
    }

You can also take a look:

https://github.com/d3/d3-interpolate/blob/master/src/transform/decompose.js#L12-L25

like image 27
Ievgen Avatar answered Oct 19 '22 15:10

Ievgen