26

how can i extract rotation, scale and translation values from 2d transformation matrix? i mean a have a 2d transformation

matrix = [1, 0, 0, 1, 0, 0]

matrix.rotate(45 / 180 * PI)
matrix.scale(3, 4)
matrix.translate(50, 100)
matrix.rotate(30 / 180 * PI)
matrix.scale(-2, 4)

now my matrix have values [a, b, c, d, tx, ty]

lets forget about the processes above and imagine that we have only the values a, b, c, d, tx, ty

how can i find total rotation and scale values via a, b, c, d, tx, ty

sorry for my english

Thanks your advance

EDIT

I think it should be an answer somewhere...

i just tried in Flash Builder (AS3) like this

   var m:Matrix = new Matrix;
   m.rotate(.25 * Math.PI);
   m.scale(4, 5);
   m.translate(100, 50);
   m.rotate(.33 * Math.PI);
   m.scale(-3, 2.5);

   var shape:Shape = new Shape;
   shape.transform.matrix = m;

   trace(shape.x, shape.y, shape.scaleX, shape.scaleY, shape.rotation);

and the output is:

x = -23.6 
y = 278.8 
scaleX = 11.627334873920528 
scaleY = -13.54222263865791 
rotation = 65.56274134518259 (in degrees)
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
Tolgahan Albayrak
  • 1,749
  • 1
  • 13
  • 13

4 Answers4

46

Not all values of a,b,c,d,tx,ty will yield a valid rotation sequence. I assume the above values are part of a 3x3 homogeneous rotation matrix in 2D

    | a  b  tx |
A = | c  d  ty |
    | 0  0  1  |

which transforms the coordinates [x, y, 1] into:

[x', y', 1] = A * |x|
                  |y|
                  |z|
  • Thus set the traslation into [dx, dy]=[tx, ty]
  • The scale is sx = sqrt(a² + c²) and sy = sqrt(b² + d²)
  • The rotation angle is t = atan(c/d) or t = atan(-b/a) as also they should be the same.

Otherwise you don't have a valid rotation matrix.


The above transformation is expanded to:

x' = tx + sx (x Cos θ - y Sin θ)
y' = ty + sy (x Sin θ + y Cos θ)

when the order is rotation, followed by scale and then translation.

legends2k
  • 31,634
  • 25
  • 118
  • 222
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • The scale operation may be different in each direction ... the scaling is a vector ... – Dr. belisarius Dec 05 '10 at 21:55
  • 3
    thank you for rotation and translation.. about scale, we got a calculated single value for scaling (s=sqrt(b^2+d^2)) is it possible to find scaleX and scaleY values? – Tolgahan Albayrak Dec 05 '10 at 21:55
  • I updated the math with 2d scaling. I missed that in the original posting (my bad). – John Alexiou Dec 05 '10 at 22:00
  • thank you again.. but in this method, sx and sy always will be positive because of power 2. what about negative scale values? – Tolgahan Albayrak Dec 05 '10 at 22:12
  • may be we shoul multiply the sx and sy by the signum of component division.. i mean sx = sx * sgn(a/b), sy = sy * sgn(c/d) – Tolgahan Albayrak Dec 05 '10 at 22:17
  • 2
    remember `sign(a)=sign(sx)` and `sign(b)=sign(sy)` due to the nature of the `cos()` function. – John Alexiou Dec 05 '10 at 22:32
  • in (a=4.810188218418486, b=10.58569820374103, c=13.4489075059838, d=-1.5870322791938274, tx=-23.60112067451982, ty=278.8156837197823) matrix flash-as3 calculates sy as negative.. and negative values of this matrix are d and tx components.. so in some ways, this negative component signums are definetely used, but how? – Tolgahan Albayrak Dec 05 '10 at 22:39
  • Thats because they are using *voodo* math :-). Maybe we should add flash to the tags and let the community answer this specifically. With the a,b,c,d values given I get inconsistent rotation. Maybe they are using a different convention in terms of the sequence of operations, or a different arrangement for the [a,b,c,d] rotation matrix. – John Alexiou Dec 05 '10 at 22:44
  • did you find the result like "70.31970570940231" ? still there are sth wrong :) – Tolgahan Albayrak Dec 05 '10 at 22:54
  • Pretty sure it's supposed to be ##sx=sqrt(a^2+c^2)## and ##sy=sqrt(b^2+d^2)##. And ##t=atan(-b/d)##. Can anyone confirm? I was getting wrong answers when calculating scale/rotation from the ##Matrix## (http://developer.android.com/reference/android/graphics/Matrix.html) of an Android View that had been scaled and rotated using the above formulas but the ones in this comment work for me. – Foxichu Jan 08 '15 at 05:32
  • There are many different conventions used out there and the platform you have differs from the _OP_ situation. – John Alexiou Jan 08 '15 at 14:39
  • Is that actually the case? I wouldn't think the rules of linear algebra and matrix multiplication would change depending on what platform you're on. – Foxichu Jan 08 '15 at 19:51
  • 1
    The conventions change. Like if using a left hand system, or right hand system. Or pre-multiply vs. post-multiply the transfrmations. Or representing the elements in row major vs. column major order. Or storing the last row of an affine transformation (3×3 planar) vs not storing (2×3 planar). And there is bound to be more. – John Alexiou Jan 08 '15 at 21:43
  • @Foxichu Yes, that's right. ja72 seems to have followed the column vector convention but mixed row vector convention halfway leading to the incorrect results. Fixed now! – legends2k Mar 25 '15 at 10:35
  • 1
    @ja72 Agree that there're different conventions used but in the answer, you've used the column vector convention used in most books for `A`. However, the multiplication with a row vector means you're multiplying `3 x 3 * 1 x 3` which is invalid. I've taken the liberty to fix it by making it a column vector, I hope it's ok. – legends2k Mar 25 '15 at 10:40
7

The term for this is matrix decomposition. Here is a solution that includes skew as described by Frédéric Wang.

function decompose_2d_matrix(mat) {
  var a = mat[0];
  var b = mat[1];
  var c = mat[2];
  var d = mat[3];
  var e = mat[4];
  var f = mat[5];

  var delta = a * d - b * c;

  let result = {
    translation: [e, f],
    rotation: 0,
    scale: [0, 0],
    skew: [0, 0],
  };

  // Apply the QR-like decomposition.
  if (a != 0 || b != 0) {
    var r = Math.sqrt(a * a + b * b);
    result.rotation = b > 0 ? Math.acos(a / r) : -Math.acos(a / r);
    result.scale = [r, delta / r];
    result.skew = [Math.atan((a * c + b * d) / (r * r)), 0];
  } else if (c != 0 || d != 0) {
    var s = Math.sqrt(c * c + d * d);
    result.rotation =
      Math.PI / 2 - (d > 0 ? Math.acos(-c / s) : -Math.acos(c / s));
    result.scale = [delta / s, s];
    result.skew = [0, Math.atan((a * c + b * d) / (s * s))];
  } else {
    // a = b = c = d = 0
  }

  return result;
}
Simon Epskamp
  • 8,813
  • 3
  • 53
  • 58
  • This function does work, but I found that if `rotate(270)` is set, the final output angle of this function is `-90`. Although this is look like the same, but the animation of `270` and `-90` in CSS is different, because it involves the direction of rotation. Is there any way to solve this problem ? – XJ.Chen Mar 08 '22 at 08:57
7

I ran into this problem today and found the easiest solution to transform a point using the matrix. This way, you can extract the translation first, then rotation and scaling.

This only works if x and y are always scaled the same (uniform scaling).

Given your matrix m which has undergone a series of transforms,

var translate:Point;
var rotate:Number;
var scale:Number;

// extract translation
var p:Point = new Point();
translate = m.transformPoint(p);
m.translate( -translate.x, -translate.y);

// extract (uniform) scale
p.x = 1.0;
p.y = 0.0;
p = m.transformPoint(p);
scale = p.length;

// and rotation
rotate = Math.atan2(p.y, p.x);

There you go!

bugshake
  • 71
  • 1
  • 2
4

If in scaling you'd scaled by the same amount in x and in y, then the determinant of the matrix, i.e. ad-bc, which tells you the area multiplier would tell you the linear change of scale too - it would be the square root of the determinant. atan( b/a ) or better atan2( b,a ) would tell you the total angle you have rotated through.

However, as your scaling isn't uniform, there is usually not going to be a way to condense your series of rotations and scaling to a single rotation followed by a single non-uniform scaling in x and y.

James Crook
  • 1,600
  • 12
  • 17