--- 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