Bug 1272549 - Part 5: Move decompose matrix to nsStyleTransformMatrix. draft
authorBoris Chiou <boris.chiou@gmail.com>
Tue, 04 Oct 2016 15:00:31 +0800
changeset 428938 a77ab9f5d0e076783bb0b1cff449e8f5e28a2aad
parent 428937 8b8a31b7c1dacda89ecf50117da417181c3f3b61
child 428939 fbf8f73e3a7662c5f36c0c46d68d08cd8f80d1a4
push id33433
push userbmo:boris.chiou@gmail.com
push dateMon, 24 Oct 2016 19:01:16 +0000
bugs1272549
milestone52.0a1
Bug 1272549 - Part 5: Move decompose matrix to nsStyleTransformMatrix. Move Decompose2DMatrix and Decompose3DMatrix into nsStyleTransformMatrix and remove some trailing spaces. Both AddWeighted and ComputeDistance need Decompose2DMatrix and Decompose3DMatrix on transform property, and both decomposition functions are related to nsStyleTransformMatrix, so we move them into nsStyleTransformMatrix to make StyleAnimationValue more concise. MozReview-Commit-ID: 5aVK7971rDD
layout/style/StyleAnimationValue.cpp
layout/style/nsStyleTransformMatrix.cpp
layout/style/nsStyleTransformMatrix.h
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -32,16 +32,18 @@
 #include "gfxQuaternion.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::gfx;
+using nsStyleTransformMatrix::Decompose2DMatrix;
+using nsStyleTransformMatrix::Decompose3DMatrix;
 
 // HELPER METHODS
 // --------------
 /*
  * Given two units, this method returns a common unit that they can both be
  * converted into, if possible.  This is intended to facilitate
  * interpolation, distance-computation, and addition between "similar" units.
  *
@@ -1604,300 +1606,16 @@ StyleAnimationValue::AppendTransformFunc
   item->mValue.SetArrayValue(arr, eCSSUnit_Function);
 
   *aListTail = item;
   aListTail = &item->mNext;
 
   return arr.forget();
 }
 
-/*
- * The relevant section of the transitions specification:
- * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
- * defers all of the details to the 2-D and 3-D transforms specifications.
- * For the 2-D transforms specification (all that's relevant for us, right
- * now), the relevant section is:
- * http://dev.w3.org/csswg/css3-2d-transforms/#animation
- * This, in turn, refers to the unmatrix program in Graphics Gems,
- * available from http://tog.acm.org/resources/GraphicsGems/ , and in
- * particular as the file GraphicsGems/gemsii/unmatrix.c
- * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
- *
- * The unmatrix reference is for general 3-D transform matrices (any of the
- * 16 components can have any value).
- *
- * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
- *
- * [ A C E ]
- * [ B D F ]
- * [ 0 0 1 ]
- *
- * For that case, I believe the algorithm in unmatrix reduces to:
- *
- *  (1) If A * D - B * C == 0, the matrix is singular.  Fail.
- *
- *  (2) Set translation components (Tx and Ty) to the translation parts of
- *      the matrix (E and F) and then ignore them for the rest of the time.
- *      (For us, E and F each actually consist of three constants:  a
- *      length, a multiplier for the width, and a multiplier for the
- *      height.  This actually requires its own decomposition, but I'll
- *      keep that separate.)
- *
- *  (3) Let the X scale (Sx) be sqrt(A^2 + B^2).  Then divide both A and B
- *      by it.
- *
- *  (4) Let the XY shear (K) be A * C + B * D.  From C, subtract A times
- *      the XY shear.  From D, subtract B times the XY shear.
- *
- *  (5) Let the Y scale (Sy) be sqrt(C^2 + D^2).  Divide C, D, and the XY
- *      shear (K) by it.
- *
- *  (6) At this point, A * D - B * C is either 1 or -1.  If it is -1,
- *      negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
- *      (Alternatively, we could negate the XY shear (K) and the Y scale
- *      (Sy).)
- *
- *  (7) Let the rotation be R = atan2(B, A).
- *
- * Then the resulting decomposed transformation is:
- *
- *   translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
- *
- * An interesting result of this is that all of the simple transform
- * functions (i.e., all functions other than matrix()), in isolation,
- * decompose back to themselves except for:
- *   'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
- *   to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
- *   alternate sign possibilities that would get fixed in step 6):
- *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
- *     Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
- *     In step 4, the XY shear is sin(φ).
- *     Thus, after step 4, C = -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ).
- *     Thus, in step 5, the Y scale is sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ).
- *     Thus, after step 5, C = -sin(φ), D = cos(φ), and the XY shear is tan(φ).
- *     Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
- *     In step 7, the rotation is thus φ.
- *
- *   skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
- *   to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
- *   the alternate sign possibilities that would get fixed in step 6):
- *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
- *     Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
- *     In step 4, the XY shear is cos(φ)tan(θ) + sin(φ).
- *     Thus, after step 4,
- *     C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
- *     D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
- *     Thus, in step 5, the Y scale is sqrt(C² + D²) =
- *     sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
- *          2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
- *          (sin²(φ)cos²(φ) + cos⁴(φ))) =
- *     sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
- *     cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
- *     we avoid flipping in step 6).
- *     After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
- *     (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
- *     (dividing both numerator and denominator by cos(φ))
- *     (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
- *     (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
- *     Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
- *     In step 7, the rotation is thus φ.
- *
- *     To check this result, we can multiply things back together:
- *
- *     [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ)    0   ]
- *     [ sin(φ)  cos(φ) ] [ 0      1     ] [   0    cos(φ) ]
- *
- *     [ cos(φ)      cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ)    0   ]
- *     [ sin(φ)      sin(φ)tan(θ + φ) + cos(φ) ] [   0    cos(φ) ]
- *
- *     but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
- *     cos(φ)tan(θ + φ) - sin(φ)
- *      = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
- *      = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
- *      = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
- *      = tan(θ) (cos(φ) + sin(φ)tan(φ))
- *      = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
- *      = tan(θ) sec(φ)
- *     and
- *     sin(φ)tan(θ + φ) + cos(φ)
- *      = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
- *      = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
- *      = sec(φ) (sin²(φ) + cos²(φ))
- *      = sec(φ)
- *     so the above is:
- *     [ cos(φ)  tan(θ) sec(φ) ] [ sec(φ)    0   ]
- *     [ sin(φ)     sec(φ)     ] [   0    cos(φ) ]
- *
- *     [    1   tan(θ) ]
- *     [ tan(φ)    1   ]
- */
-
-/*
- * Decompose2DMatrix implements the above decomposition algorithm.
- */
-
-#define XYSHEAR 0
-#define XZSHEAR 1
-#define YZSHEAR 2
-
-static bool
-Decompose2DMatrix(const Matrix &aMatrix, Point3D &aScale,
-                  float aShear[3], gfxQuaternion &aRotate,
-                  Point3D &aTranslate)
-{
-  float A = aMatrix._11,
-        B = aMatrix._12,
-        C = aMatrix._21,
-        D = aMatrix._22;
-  if (A * D == B * C) {
-    // singular matrix
-    return false;
-  }
-
-  float scaleX = sqrt(A * A + B * B);
-  A /= scaleX;
-  B /= scaleX;
-
-  float XYshear = A * C + B * D;
-  C -= A * XYshear;
-  D -= B * XYshear;
-
-  float scaleY = sqrt(C * C + D * D);
-  C /= scaleY;
-  D /= scaleY;
-  XYshear /= scaleY;
-
-  // A*D - B*C should now be 1 or -1
-  NS_ASSERTION(0.99 < Abs(A*D - B*C) && Abs(A*D - B*C) < 1.01,
-               "determinant should now be 1 or -1");
-  if (A * D < B * C) {
-    A = -A;
-    B = -B;
-    C = -C;
-    D = -D;
-    XYshear = -XYshear;
-    scaleX = -scaleX;
-  }
-
-  float rotate = atan2f(B, A);
-  aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2));
-  aShear[XYSHEAR] = XYshear;
-  aScale.x = scaleX;
-  aScale.y = scaleY;
-  aTranslate.x = aMatrix._31;
-  aTranslate.y = aMatrix._32;
-  return true;
-}
-
-/**
- * Implementation of the unmatrix algorithm, specified by:
- *
- * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix
- *
- * This, in turn, refers to the unmatrix program in Graphics Gems,
- * available from http://tog.acm.org/resources/GraphicsGems/ , and in
- * particular as the file GraphicsGems/gemsii/unmatrix.c
- * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
- */
-static bool
-Decompose3DMatrix(const Matrix4x4 &aMatrix, Point3D &aScale,
-                  float aShear[3], gfxQuaternion &aRotate,
-                  Point3D &aTranslate, Point4D &aPerspective)
-{
-  Matrix4x4 local = aMatrix;
-
-  if (local[3][3] == 0) {
-    return false;
-  }
-  /* Normalize the matrix */
-  local.Normalize();
-
-  /**
-   * perspective is used to solve for perspective, but it also provides
-   * an easy way to test for singularity of the upper 3x3 component.
-   */
-  Matrix4x4 perspective = local;
-  Point4D empty(0, 0, 0, 1);
-  perspective.SetTransposedVector(3, empty);
-
-  if (perspective.Determinant() == 0.0) {
-    return false;
-  }
-
-  /* First, isolate perspective. */
-  if (local[0][3] != 0 || local[1][3] != 0 ||
-      local[2][3] != 0) {
-    /* aPerspective is the right hand side of the equation. */
-    aPerspective = local.TransposedVector(3);
-
-    /**
-     * Solve the equation by inverting perspective and multiplying
-     * aPerspective by the inverse.
-     */
-    perspective.Invert();
-    aPerspective = perspective.TransposeTransform4D(aPerspective);
-
-    /* Clear the perspective partition */
-    local.SetTransposedVector(3, empty);
-  } else {
-    aPerspective = Point4D(0, 0, 0, 1);
-  }
-
-  /* Next take care of translation */
-  for (int i = 0; i < 3; i++) {
-    aTranslate[i] = local[3][i];
-    local[3][i] = 0;
-  }
-
-  /* Now get scale and shear. */
-
-  /* Compute X scale factor and normalize first row. */
-  aScale.x = local[0].Length();
-  local[0] /= aScale.x;
-
-  /* Compute XY shear factor and make 2nd local orthogonal to 1st. */
-  aShear[XYSHEAR] = local[0].DotProduct(local[1]);
-  local[1] -= local[0] * aShear[XYSHEAR];
-
-  /* Now, compute Y scale and normalize 2nd local. */
-  aScale.y = local[1].Length();
-  local[1] /= aScale.y;
-  aShear[XYSHEAR] /= aScale.y;
-
-  /* Compute XZ and YZ shears, make 3rd local orthogonal */
-  aShear[XZSHEAR] = local[0].DotProduct(local[2]);
-  local[2] -= local[0] * aShear[XZSHEAR];
-  aShear[YZSHEAR] = local[1].DotProduct(local[2]);
-  local[2] -= local[1] * aShear[YZSHEAR];
-
-  /* Next, get Z scale and normalize 3rd local. */
-  aScale.z = local[2].Length();
-  local[2] /= aScale.z;
-
-  aShear[XZSHEAR] /= aScale.z;
-  aShear[YZSHEAR] /= aScale.z;
-
-  /**
-   * At this point, the matrix (in locals) is orthonormal.
-   * Check for a coordinate system flip.  If the determinant
-   * is -1, then negate the matrix and the scaling factors.
-   */
-  if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) {
-    aScale *= -1;
-    for (int i = 0; i < 3; i++) {
-      local[i] *= -1;
-    }
-  }
-
-  /* Now, get the rotations out */
-  aRotate = gfxQuaternion(local);
-
-  return true;
-}
-
 template<typename T>
 T InterpolateNumerically(const T& aOne, const T& aTwo, double aCoeff)
 {
   return aOne + (aTwo - aOne) * aCoeff;
 }
 
 
 /* static */ Matrix4x4
@@ -1942,17 +1660,18 @@ StyleAnimationValue::InterpolateTransfor
   result.PreTranslate(translate.x, translate.y, translate.z);
 
   gfxQuaternion q3 = rotate1.Slerp(rotate2, aProgress);
   Matrix4x4 rotate = q3.ToMatrix();
   if (!rotate.IsIdentity()) {
       result = rotate * result;
   }
 
-  // TODO: Would it be better to interpolate these as angles? How do we convert back to angles?
+  // TODO: Would it be better to interpolate these as angles?
+  //       How do we convert back to angles?
   float yzshear =
     InterpolateNumerically(shear1[YZSHEAR], shear2[YZSHEAR], aProgress);
   if (yzshear != 0.0) {
     result.SkewYZ(yzshear);
   }
 
   float xzshear =
     InterpolateNumerically(shear1[XZSHEAR], shear2[XZSHEAR], aProgress);
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -11,16 +11,17 @@
 #include "nsCSSValue.h"
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
 #include "nsRuleNode.h"
 #include "nsSVGUtils.h"
 #include "nsCSSKeywords.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "gfxMatrix.h"
+#include "gfxQuaternion.h"
 
 #include <limits>
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 namespace nsStyleTransformMatrix {
 
@@ -219,17 +220,17 @@ ProcessMatrix(Matrix4x4& aMatrix,
                                    &aRefBox, &TransformReferenceBox::Width);
   result._32 = ProcessTranslatePart(aData->Item(6),
                                    aContext, aPresContext, aConditions,
                                    &aRefBox, &TransformReferenceBox::Height);
 
   aMatrix = result * aMatrix;
 }
 
-static void 
+static void
 ProcessMatrix3D(Matrix4x4& aMatrix,
                 const nsCSSValue::Array* aData,
                 nsStyleContext* aContext,
                 nsPresContext* aPresContext,
                 RuleNodeCacheConditions& aConditions,
                 TransformReferenceBox& aRefBox)
 {
   NS_PRECONDITION(aData->Count() == 17, "Invalid array!");
@@ -330,17 +331,17 @@ ProcessTranslateY(Matrix4x4& aMatrix,
   Point3D temp;
 
   temp.y = ProcessTranslatePart(aData->Item(1),
                                 aContext, aPresContext, aConditions,
                                 &aRefBox, &TransformReferenceBox::Height);
   aMatrix.PreTranslate(temp);
 }
 
-static void 
+static void
 ProcessTranslateZ(Matrix4x4& aMatrix,
                   const nsCSSValue::Array* aData,
                   nsStyleContext* aContext,
                   nsPresContext* aPresContext,
                   RuleNodeCacheConditions& aConditions)
 {
   NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
 
@@ -403,18 +404,18 @@ ProcessTranslate3D(Matrix4x4& aMatrix,
                                 nullptr);
 
   aMatrix.PreTranslate(temp);
 }
 
 /* Helper function to set up a scale matrix. */
 static void
 ProcessScaleHelper(Matrix4x4& aMatrix,
-                   float aXScale, 
-                   float aYScale, 
+                   float aXScale,
+                   float aYScale,
                    float aZScale)
 {
   aMatrix.PreScale(aXScale, aYScale, aZScale);
 }
 
 /* Process a scalex function. */
 static void
 ProcessScaleX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
@@ -455,17 +456,17 @@ ProcessScale(Matrix4x4& aMatrix, const n
   NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
   /* We either have one element or two.  If we have one, it's for both X and Y.
    * Otherwise it's one for each.
    */
   const nsCSSValue& scaleX = aData->Item(1);
   const nsCSSValue& scaleY = (aData->Count() == 2 ? scaleX :
                               aData->Item(2));
 
-  ProcessScaleHelper(aMatrix, 
+  ProcessScaleHelper(aMatrix,
                      scaleX.GetFloatValue(),
                      scaleY.GetFloatValue(),
                      1.0f);
 }
 
 /* Helper function that, given a set of angles, constructs the appropriate
  * skew matrix.
  */
@@ -540,17 +541,17 @@ ProcessRotate3D(Matrix4x4& aMatrix, cons
   float z = aData->Item(3).GetFloatValue();
 
   Matrix4x4 temp;
   temp.SetRotateAxisAngle(x, y, z, theta);
 
   aMatrix = temp * aMatrix;
 }
 
-static void 
+static void
 ProcessPerspective(Matrix4x4& aMatrix,
                    const nsCSSValue::Array* aData,
                    nsStyleContext *aContext,
                    nsPresContext *aPresContext,
                    RuleNodeCacheConditions& aConditions)
 {
   NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
 
@@ -661,17 +662,17 @@ MatrixForTransformFunction(Matrix4x4& aM
     break;
   case eCSSKeyword_interpolatematrix:
     ProcessInterpolateMatrix(aMatrix, aData, aContext, aPresContext,
                              aConditions, aRefBox,
                              aContains3dTransform);
     break;
   case eCSSKeyword_perspective:
     *aContains3dTransform = true;
-    ProcessPerspective(aMatrix, aData, aContext, aPresContext, 
+    ProcessPerspective(aMatrix, aData, aContext, aPresContext,
                        aConditions);
     break;
   default:
     NS_NOTREACHED("Unknown transform function!");
   }
 }
 
 /**
@@ -742,9 +743,294 @@ ReadTransforms(const nsCSSValueList* aLi
 
   float scale = float(nsPresContext::AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
   result.PreScale(1/scale, 1/scale, 1/scale);
   result.PostScale(scale, scale, scale);
 
   return result;
 }
 
+/*
+ * The relevant section of the transitions specification:
+ * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
+ * defers all of the details to the 2-D and 3-D transforms specifications.
+ * For the 2-D transforms specification (all that's relevant for us, right
+ * now), the relevant section is:
+ * http://dev.w3.org/csswg/css3-2d-transforms/#animation
+ * This, in turn, refers to the unmatrix program in Graphics Gems,
+ * available from http://tog.acm.org/resources/GraphicsGems/ , and in
+ * particular as the file GraphicsGems/gemsii/unmatrix.c
+ * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
+ *
+ * The unmatrix reference is for general 3-D transform matrices (any of the
+ * 16 components can have any value).
+ *
+ * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
+ *
+ * [ A C E ]
+ * [ B D F ]
+ * [ 0 0 1 ]
+ *
+ * For that case, I believe the algorithm in unmatrix reduces to:
+ *
+ *  (1) If A * D - B * C == 0, the matrix is singular.  Fail.
+ *
+ *  (2) Set translation components (Tx and Ty) to the translation parts of
+ *      the matrix (E and F) and then ignore them for the rest of the time.
+ *      (For us, E and F each actually consist of three constants:  a
+ *      length, a multiplier for the width, and a multiplier for the
+ *      height.  This actually requires its own decomposition, but I'll
+ *      keep that separate.)
+ *
+ *  (3) Let the X scale (Sx) be sqrt(A^2 + B^2).  Then divide both A and B
+ *      by it.
+ *
+ *  (4) Let the XY shear (K) be A * C + B * D.  From C, subtract A times
+ *      the XY shear.  From D, subtract B times the XY shear.
+ *
+ *  (5) Let the Y scale (Sy) be sqrt(C^2 + D^2).  Divide C, D, and the XY
+ *      shear (K) by it.
+ *
+ *  (6) At this point, A * D - B * C is either 1 or -1.  If it is -1,
+ *      negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
+ *      (Alternatively, we could negate the XY shear (K) and the Y scale
+ *      (Sy).)
+ *
+ *  (7) Let the rotation be R = atan2(B, A).
+ *
+ * Then the resulting decomposed transformation is:
+ *
+ *   translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
+ *
+ * An interesting result of this is that all of the simple transform
+ * functions (i.e., all functions other than matrix()), in isolation,
+ * decompose back to themselves except for:
+ *   'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
+ *   to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
+ *   alternate sign possibilities that would get fixed in step 6):
+ *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
+ *     Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
+ *     In step 4, the XY shear is sin(φ).
+ *     Thus, after step 4, C = -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ).
+ *     Thus, in step 5, the Y scale is sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ).
+ *     Thus, after step 5, C = -sin(φ), D = cos(φ), and the XY shear is tan(φ).
+ *     Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
+ *     In step 7, the rotation is thus φ.
+ *
+ *   skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
+ *   to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
+ *   the alternate sign possibilities that would get fixed in step 6):
+ *     In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
+ *     Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
+ *     In step 4, the XY shear is cos(φ)tan(θ) + sin(φ).
+ *     Thus, after step 4,
+ *     C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
+ *     D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
+ *     Thus, in step 5, the Y scale is sqrt(C² + D²) =
+ *     sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
+ *          2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
+ *          (sin²(φ)cos²(φ) + cos⁴(φ))) =
+ *     sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
+ *     cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
+ *     we avoid flipping in step 6).
+ *     After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
+ *     (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
+ *     (dividing both numerator and denominator by cos(φ))
+ *     (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
+ *     (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
+ *     Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
+ *     In step 7, the rotation is thus φ.
+ *
+ *     To check this result, we can multiply things back together:
+ *
+ *     [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ)    0   ]
+ *     [ sin(φ)  cos(φ) ] [ 0      1     ] [   0    cos(φ) ]
+ *
+ *     [ cos(φ)      cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ)    0   ]
+ *     [ sin(φ)      sin(φ)tan(θ + φ) + cos(φ) ] [   0    cos(φ) ]
+ *
+ *     but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
+ *     cos(φ)tan(θ + φ) - sin(φ)
+ *      = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
+ *      = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
+ *      = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
+ *      = tan(θ) (cos(φ) + sin(φ)tan(φ))
+ *      = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
+ *      = tan(θ) sec(φ)
+ *     and
+ *     sin(φ)tan(θ + φ) + cos(φ)
+ *      = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
+ *      = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
+ *      = sec(φ) (sin²(φ) + cos²(φ))
+ *      = sec(φ)
+ *     so the above is:
+ *     [ cos(φ)  tan(θ) sec(φ) ] [ sec(φ)    0   ]
+ *     [ sin(φ)     sec(φ)     ] [   0    cos(φ) ]
+ *
+ *     [    1   tan(θ) ]
+ *     [ tan(φ)    1   ]
+ */
+
+/*
+ * Decompose2DMatrix implements the above decomposition algorithm.
+ */
+
+bool
+Decompose2DMatrix(const Matrix& aMatrix,
+                  Point3D& aScale,
+                  float aShear[3],
+                  gfxQuaternion& aRotate,
+                  Point3D& aTranslate)
+{
+  float A = aMatrix._11,
+        B = aMatrix._12,
+        C = aMatrix._21,
+        D = aMatrix._22;
+  if (A * D == B * C) {
+    // singular matrix
+    return false;
+  }
+
+  float scaleX = sqrt(A * A + B * B);
+  A /= scaleX;
+  B /= scaleX;
+
+  float XYshear = A * C + B * D;
+  C -= A * XYshear;
+  D -= B * XYshear;
+
+  float scaleY = sqrt(C * C + D * D);
+  C /= scaleY;
+  D /= scaleY;
+  XYshear /= scaleY;
+
+  // A*D - B*C should now be 1 or -1
+  NS_ASSERTION(0.99 < Abs(A*D - B*C) && Abs(A*D - B*C) < 1.01,
+               "determinant should now be 1 or -1");
+  if (A * D < B * C) {
+    A = -A;
+    B = -B;
+    C = -C;
+    D = -D;
+    XYshear = -XYshear;
+    scaleX = -scaleX;
+  }
+
+  float rotate = atan2f(B, A);
+  aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2));
+  aShear[XYSHEAR] = XYshear;
+  aScale.x = scaleX;
+  aScale.y = scaleY;
+  aTranslate.x = aMatrix._31;
+  aTranslate.y = aMatrix._32;
+  return true;
+}
+
+/**
+ * Implementation of the unmatrix algorithm, specified by:
+ *
+ * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix
+ *
+ * This, in turn, refers to the unmatrix program in Graphics Gems,
+ * available from http://tog.acm.org/resources/GraphicsGems/ , and in
+ * particular as the file GraphicsGems/gemsii/unmatrix.c
+ * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
+ */
+bool
+Decompose3DMatrix(const Matrix4x4& aMatrix,
+                  Point3D& aScale,
+                  float aShear[3],
+                  gfxQuaternion& aRotate,
+                  Point3D& aTranslate,
+                  Point4D& aPerspective)
+{
+  Matrix4x4 local = aMatrix;
+
+  if (local[3][3] == 0) {
+    return false;
+  }
+  /* Normalize the matrix */
+  local.Normalize();
+
+  /**
+   * perspective is used to solve for perspective, but it also provides
+   * an easy way to test for singularity of the upper 3x3 component.
+   */
+  Matrix4x4 perspective = local;
+  Point4D empty(0, 0, 0, 1);
+  perspective.SetTransposedVector(3, empty);
+
+  if (perspective.Determinant() == 0.0) {
+    return false;
+  }
+
+  /* First, isolate perspective. */
+  if (local[0][3] != 0 || local[1][3] != 0 ||
+      local[2][3] != 0) {
+    /* aPerspective is the right hand side of the equation. */
+    aPerspective = local.TransposedVector(3);
+
+    /**
+     * Solve the equation by inverting perspective and multiplying
+     * aPerspective by the inverse.
+     */
+    perspective.Invert();
+    aPerspective = perspective.TransposeTransform4D(aPerspective);
+
+    /* Clear the perspective partition */
+    local.SetTransposedVector(3, empty);
+  } else {
+    aPerspective = Point4D(0, 0, 0, 1);
+  }
+
+  /* Next take care of translation */
+  for (int i = 0; i < 3; i++) {
+    aTranslate[i] = local[3][i];
+    local[3][i] = 0;
+  }
+
+  /* Now get scale and shear. */
+
+  /* Compute X scale factor and normalize first row. */
+  aScale.x = local[0].Length();
+  local[0] /= aScale.x;
+
+  /* Compute XY shear factor and make 2nd local orthogonal to 1st. */
+  aShear[XYSHEAR] = local[0].DotProduct(local[1]);
+  local[1] -= local[0] * aShear[XYSHEAR];
+
+  /* Now, compute Y scale and normalize 2nd local. */
+  aScale.y = local[1].Length();
+  local[1] /= aScale.y;
+  aShear[XYSHEAR] /= aScale.y;
+
+  /* Compute XZ and YZ shears, make 3rd local orthogonal */
+  aShear[XZSHEAR] = local[0].DotProduct(local[2]);
+  local[2] -= local[0] * aShear[XZSHEAR];
+  aShear[YZSHEAR] = local[1].DotProduct(local[2]);
+  local[2] -= local[1] * aShear[YZSHEAR];
+
+  /* Next, get Z scale and normalize 3rd local. */
+  aScale.z = local[2].Length();
+  local[2] /= aScale.z;
+
+  aShear[XZSHEAR] /= aScale.z;
+  aShear[YZSHEAR] /= aScale.z;
+
+  /**
+   * At this point, the matrix (in locals) is orthonormal.
+   * Check for a coordinate system flip.  If the determinant
+   * is -1, then negate the matrix and the scaling factors.
+   */
+  if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) {
+    aScale *= -1;
+    for (int i = 0; i < 3; i++) {
+      local[i] *= -1;
+    }
+  }
+
+  /* Now, get the rotations out */
+  aRotate = gfxQuaternion(local);
+
+  return true;
+}
+
 } // namespace nsStyleTransformMatrix
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -10,16 +10,17 @@
 #ifndef nsStyleTransformMatrix_h_
 #define nsStyleTransformMatrix_h_
 
 #include "nsCSSValue.h"
 
 class nsIFrame;
 class nsStyleContext;
 class nsPresContext;
+struct gfxQuaternion;
 struct nsRect;
 namespace mozilla {
 class RuleNodeCacheConditions;
 } // namespace mozilla
 
 /**
  * A helper to generate gfxMatrixes from css transform functions.
  */
@@ -166,11 +167,34 @@ namespace nsStyleTransformMatrix {
    */
   mozilla::gfx::Matrix4x4 ReadTransforms(const nsCSSValueList* aList,
                                          nsStyleContext* aContext,
                                          nsPresContext* aPresContext,
                                          mozilla::RuleNodeCacheConditions& aConditions,
                                          TransformReferenceBox& aBounds,
                                          float aAppUnitsPerMatrixUnit,
                                          bool* aContains3dTransform);
+
+  // Shear type for decomposition.
+  #define XYSHEAR 0
+  #define XZSHEAR 1
+  #define YZSHEAR 2
+
+  /*
+   * Implements the 2d transform matrix decomposition algorithm.
+   */
+  bool Decompose2DMatrix(const mozilla::gfx::Matrix& aMatrix,
+                         mozilla::gfx::Point3D& aScale,
+                         float aShear[3],
+                         gfxQuaternion& aRotate,
+                         mozilla::gfx::Point3D& aTranslate);
+  /*
+   * Implements the 3d transform matrix decomposition algorithm.
+   */
+  bool Decompose3DMatrix(const mozilla::gfx::Matrix4x4& aMatrix,
+                         mozilla::gfx::Point3D& aScale,
+                         float aShear[3],
+                         gfxQuaternion& aRotate,
+                         mozilla::gfx::Point3D& aTranslate,
+                         mozilla::gfx::Point4D& aPerspective);
 } // namespace nsStyleTransformMatrix
 
 #endif