17

I have the the following Transform Matrix in CSS

// rotate the element 60deg
element.style.transform = "matrix(0.5,0.866025,-0.866025,0.5,0,0)"

And i can find the rotation using this...

// where a = [0.710138,0.502055,-0.57735,1,0,0]
var rotation = ((180/Math.PI) * Math.atan2( ((0*a[2])+(1*a[3])),((0*a[0])-(1*a[1]))) - 90
console.log(rotation); // ~60

Similarly for skew if...

// skew(30deg,-50deg) 
element.style.transform = "matrix(1,-1.19175,0.57735,1,0,0)"

var skewY = ((180/Math.PI) * Math.atan2( ((0*a[2])+(1*a[3])),((0*a[0])-(1*a[1]))) - 90;
var skewX = (180/Math.PI) * Math.atan2( ((1*a[2])+(0*a[3])),((1*a[0])-(0*a[1])));

console.log([skewX,skewY]); // ~= [30,-50] 

However as soon as i use both skew and rotation everything goes weird not least because the formula for rotation is identical to that of skew... so the formulas can't be right.

How do i determine both rotation & skew where both attributes have been applied and all i know is the Matrix Transform.

Also scale messed up my skew values, which i dont think it should.

AndreaBogazzi
  • 14,323
  • 3
  • 38
  • 63
Drew
  • 1,420
  • 1
  • 11
  • 14

2 Answers2

27

I needed same functionality and today I ended up with this code that works very good.

I took inspiration from here: https://www.youtube.com/watch?v=51MRHjKSbtk and from the answer below, without the hint QR decomposition i would never find it out

I worked on a 2x2 case, i will try to expand to 2x3 to include also translations. But it should be easy

var a = [a, b, c, d, e, f];
var qrDecompone = function(a) {
  var angle = Math.atan2(a[1], a[0]),
      denom = Math.pow(a[0], 2) + Math.pow(a[1], 2),
      scaleX = Math.sqrt(denom),
      scaleY = (a[0] * a[3] - a[2] * a[1]) / scaleX,
      skewX = Math.atan2(a[0] * a[2] + a[1] * a[3], denom);
  return {
    angle: angle / (Math.PI / 180),  // this is rotation angle in degrees
    scaleX: scaleX,                  // scaleX factor  
    scaleY: scaleY,                  // scaleY factor
    skewX: skewX / (Math.PI / 180),  // skewX angle degrees
    skewY: 0,                        // skewY angle degrees
    translateX: a[4],                // translation point  x
    translateY: a[5]                 // translation point  y
  };
};

It looks like that the last two items in the transformMatrix, before decomposition, are translation values. You can extract them directly.

Community
  • 1
  • 1
AndreaBogazzi
  • 14,323
  • 3
  • 38
  • 63
  • awesome! Well done :) should be the accepted answer @Drew – vidstige Oct 19 '15 at 08:36
  • Thanks AndreaBogazzi and @vidstige, i've made this the accepted answer. Great! – Drew Dec 03 '15 at 11:23
  • Does anyone know a version of this answer for 3d transforms. – user2782001 Apr 30 '16 at 17:30
  • If anyone else gets an incorrect angle value (eg: -90 instead of 90), try using the `b` component of the matrix as the first argument to atan2 call: `angle = Math.atan2(a[2], a[0])`. – ivanreese Jun 30 '16 at 23:19
  • 1
    do you get wrong angles? in wich situations? i'm using this function a lot and i do not get any. interested in understanding. – AndreaBogazzi Jul 01 '16 at 06:20
  • 1
    I'm using it to process an SVG exported from Flash, and when I used atan2(c, a) it produced incorrect rotation values, where atan2(b, a) worked for me. Question for you, Andrea: Is the order of the matrix on the first line [a, c, b...] correct, or is that a typo? All the other references I've seen, including the below answer from vidstige, list the parts in alphabetical order. I haven't tested this, but if switching to [a, b, c...], without making any other code changes, produces a valid skewX, then that typo would explain the issue I encountered. – ivanreese Jul 01 '16 at 07:03
  • 1
    @ivanreese I'm pretty sure it's a typo and the example should be `var a = [ a, b, c, d, e, f ];`. – hungerstar May 18 '17 at 17:27
  • going to check and be sure. – AndreaBogazzi May 18 '17 at 20:35
  • qrDecompose: function(a) { var angle = atan2(a[1], a[0]), denom = pow(a[0], 2) + pow(a[1], 2), scaleX = sqrt(denom), scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX, skewX = atan2(a[0] * a[2] + a[1] * a [3], denom); return { angle: angle / PiBy180, scaleX: scaleX, scaleY: scaleY, skewX: skewX / PiBy180, skewY: 0, translateX: a[4], translateY: a[5] }; }, – AndreaBogazzi May 18 '17 at 20:43
  • 1
    definitely a typo. anyway this function really changed the kind of problems i could solve in fabric.js, i m very happy of it. – AndreaBogazzi May 18 '17 at 20:45
  • Hey Andrea, good stuff! I might have found a slight problem though, I'd be thankful if you could take a look. In the case of the matrix below, your QR algorithm seems to produce different results from that of the actual mathematical Gram-Schmidt process, probably because of the "cross-product" shortcut you'd taken with line `(a[0] * a[3] - a[2] * a[1]) / scaleX`. `[-1, -1, -1, 0, 0, 0]` When you follow the process by hand on a paper, it should result in this right-upper triangular matrix: `[[SQRT2, SQRT1_2], [0, SQRT1_2]]`–however yours yields `[[SQRT2, SQRT1_2], [0, -SQRT1_2]]`. – Emre Akı Sep 17 '21 at 10:53
  • To follow up with my previous comment, although mathematically incorrect, your result makes more sense since it indicates that there is a mirroring/flip along the y-axis. Since the entries in the orthonormal Q matrix extracted from my test matrix, `[[-SQRT1_2, -SQRT1_2], [-SQRT1_2, SQRT1_2]]`, actually do not initially comply with the form `[[cosx, -sinx], [sinx, cosx]]`, without flipping the sign of the x-axis for the second vector. – Emre Akı Sep 17 '21 at 11:06
  • I think this is because canvas has an inverted y axis, numbers are top bottom. This has built specific for the canvas. Does it make sense? i remember i followed a demonstration step by step and then just simplified some stuff. – AndreaBogazzi Sep 20 '21 at 21:37
  • Also in my opinion some matrices have 2 possible solutions. The classic case is that a flipX and a flipY do often get confused with a 180 degree rotation, and that is sort of right because when the transformation is done, you can't distinguish anymore what really happened. we have 7 numbers in input ( ( left, top, scaleX, scaleY, angle, skewX, skewY ) but we store in a matrix of a 6 numbers, so something will get lost. – AndreaBogazzi Sep 20 '21 at 21:42
  • What is the order here? e.g. is it "skew first and rotation later" or "rotation first and skew later"? I can see that the translation is applied last, though – qrsngky Feb 21 '23 at 04:13
  • i think is: - translation first, then rotation, scale, skewX then skewY – AndreaBogazzi Feb 25 '23 at 21:53
14

Found the definition of your matrices here. We have the transformation matrix T

    / a b tx \
T = | c d ty |
    \ 0 0 1  /

For the following expression

element.style.transform = "matrix(a,b,c,d,tx,ty)"

In order to retrive the parameters used to build up this matrix we need to first find a decomposition of the matrix T. Assuming the skew is applied after the rotation we can find the QR-decomposition:

QR = T

The rotation will be found inside the Q matrix in the form of a pure rotation matrix. You can then use trigonometry to find out the single rotation angle, for example like so

rotation = atan2(Q21, Q11)

The skew and translation will be found in the R matrix.

    / sx   k  tx \
R = |  0  sy  ty |
    \  0   0   1 /

Where sx and sy is the scale and k represents the shear. I dont know how this shear relates to the css-skew.

I don't know if the QR decomposition is availble in javascript, but it should be easy enough to implement using the Numerical Recipes as reference.

Not a complete answer to get the parameters to create a new matrix object, but should set you off in the right direction!

vidstige
  • 12,492
  • 9
  • 66
  • 110
  • Thanks for your response vidstige. I've stalled the project for a moment. When i get time i'll continue where you left off. But its actually very simple Vector Matrix i've learnt. I say very simple... i haven't worked it out yet. :) – Drew Mar 02 '11 at 22:33
  • thank me by upvoting my answer :) It's not that simple actually, but should absolutely be doable. – vidstige Mar 03 '11 at 10:03
  • 1
    Thanks to your hints i could find the solutions Below my answer with full code to decompone the matrix. – AndreaBogazzi Oct 19 '15 at 07:50