diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 39d199ee6e6..54298b53b74 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -1058,27 +1058,53 @@ nsComputedDOMStyle::DoGetMozTransform() bounds, float(nsDeviceContext::AppUnitsPerCSSPixel())); - if (!matrix.Is2D()) { - nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue(); + PRBool is3D = !matrix.Is2D(); - /* Set it to "none." */ - val->SetIdent(eCSSKeyword_none); - return val; + nsAutoString resultString(NS_LITERAL_STRING("matrix")); + if (is3D) { + resultString.Append(NS_LITERAL_STRING("3d")); } - nsAutoString resultString(NS_LITERAL_STRING("matrix(")); + resultString.Append(NS_LITERAL_STRING("(")); resultString.AppendFloat(matrix._11); resultString.Append(NS_LITERAL_STRING(", ")); resultString.AppendFloat(matrix._12); resultString.Append(NS_LITERAL_STRING(", ")); + if (is3D) { + resultString.AppendFloat(matrix._13); + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._14); + resultString.Append(NS_LITERAL_STRING(", ")); + } resultString.AppendFloat(matrix._21); resultString.Append(NS_LITERAL_STRING(", ")); resultString.AppendFloat(matrix._22); resultString.Append(NS_LITERAL_STRING(", ")); + if (is3D) { + resultString.AppendFloat(matrix._23); + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._24); + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._31); + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._32); + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._33); + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._34); + resultString.Append(NS_LITERAL_STRING(", ")); + } resultString.AppendFloat(matrix._41); resultString.Append(NS_LITERAL_STRING("px, ")); resultString.AppendFloat(matrix._42); - resultString.Append(NS_LITERAL_STRING("px)")); + resultString.Append(NS_LITERAL_STRING("px")); + if (is3D) { + resultString.Append(NS_LITERAL_STRING(", ")); + resultString.AppendFloat(matrix._43); + resultString.Append(NS_LITERAL_STRING("px, ")); + resultString.AppendFloat(matrix._44); + } + resultString.Append(NS_LITERAL_STRING(")")); /* Create a value to hold our result. */ nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue(); diff --git a/layout/style/nsStyleAnimation.cpp b/layout/style/nsStyleAnimation.cpp index 89081e29aab..d128883f79b 100644 --- a/layout/style/nsStyleAnimation.cpp +++ b/layout/style/nsStyleAnimation.cpp @@ -53,6 +53,7 @@ #include "prlog.h" #include #include "gfxMatrix.h" +#include "gfxQuaternion.h" namespace css = mozilla::css; namespace dom = mozilla::dom; @@ -908,24 +909,43 @@ AppendTransformFunction(nsCSSKeyword aTransformFunction, nsCSSValueList**& aListTail) { PRUint32 nargs; - if (aTransformFunction == eCSSKeyword_matrix) { - nargs = 6; - } else if (aTransformFunction == eCSSKeyword_translate || - aTransformFunction == eCSSKeyword_skew || - aTransformFunction == eCSSKeyword_scale) { - nargs = 2; - } else if (aTransformFunction == eCSSKeyword_interpolatematrix) { - nargs = 4; - } else { - NS_ABORT_IF_FALSE(aTransformFunction == eCSSKeyword_translatex || - aTransformFunction == eCSSKeyword_translatey || - aTransformFunction == eCSSKeyword_scalex || - aTransformFunction == eCSSKeyword_scaley || - aTransformFunction == eCSSKeyword_skewx || - aTransformFunction == eCSSKeyword_skewy || - aTransformFunction == eCSSKeyword_rotate, - "must be a transform function"); - nargs = 1; + switch (aTransformFunction) { + case eCSSKeyword_matrix3d: + nargs = 16; + break; + case eCSSKeyword_matrix: + nargs = 6; + break; + case eCSSKeyword_rotate3d: + nargs = 4; + break; + case eCSSKeyword_interpolatematrix: + case eCSSKeyword_translate3d: + case eCSSKeyword_scale3d: + nargs = 3; + break; + case eCSSKeyword_translate: + case eCSSKeyword_skew: + case eCSSKeyword_scale: + nargs = 2; + break; + default: + NS_ERROR("must be a transform function"); + case eCSSKeyword_translatex: + case eCSSKeyword_translatey: + case eCSSKeyword_translatez: + case eCSSKeyword_scalex: + case eCSSKeyword_scaley: + case eCSSKeyword_scalez: + case eCSSKeyword_skewx: + case eCSSKeyword_skewy: + case eCSSKeyword_rotate: + case eCSSKeyword_rotatex: + case eCSSKeyword_rotatey: + case eCSSKeyword_rotatez: + case eCSSKeyword_perspective: + nargs = 1; + break; } nsRefPtr arr = nsCSSValue::Array::Create(nargs + 1); @@ -1064,12 +1084,17 @@ AppendTransformFunction(nsCSSKeyword aTransformFunction, */ /* - * DecomposeMatrix implements the non-translation parts of the above - * decomposition algorithm. + * Decompose2DMatrix implements the above decomposition algorithm. */ + +#define XYSHEAR 0 +#define XZSHEAR 1 +#define YZSHEAR 2 + static PRBool -DecomposeMatrix(const gfxMatrix &aMatrix, - float &aRotate, float &aXYShear, float &aScaleX, float &aScaleY) +Decompose2DMatrix(const gfxMatrix &aMatrix, gfxPoint3D &aScale, + float aShear[3], gfxQuaternion &aRotate, + gfxPoint3D &aTranslate) { float A = aMatrix.xx, B = aMatrix.yx, @@ -1093,7 +1118,7 @@ DecomposeMatrix(const gfxMatrix &aMatrix, D /= scaleY; XYshear /= scaleY; - // A*D - B*C should now be 1 or -1 + // A*D - B*C should now be 1 or -1 NS_ASSERTION(0.99 < NS_ABS(A*D - B*C) && NS_ABS(A*D - B*C) < 1.01, "determinant should now be 1 or -1"); if (A * D < B * C) { @@ -1105,84 +1130,200 @@ DecomposeMatrix(const gfxMatrix &aMatrix, scaleX = -scaleX; } - float rotation = atan2f(B, A); + 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.x0; + aTranslate.y = aMatrix.y0; + return PR_TRUE; +} - aRotate = rotation; - aXYShear = XYshear; - aScaleX = scaleX; - aScaleY = scaleY; +/** + * 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 PRBool +Decompose3DMatrix(const gfx3DMatrix &aMatrix, gfxPoint3D &aScale, + float aShear[3], gfxQuaternion &aRotate, + gfxPoint3D &aTranslate, gfxPointH3D &aPerspective) +{ + gfx3DMatrix local = aMatrix; + + if (local[3][3] == 0) { + return PR_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. + */ + gfx3DMatrix perspective = local; + gfxPointH3D empty(0, 0, 0, 1); + perspective.SetTransposedVector(3, empty); + + if (perspective.Determinant() == 0.0) { + return PR_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 = gfxPointH3D(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 PR_TRUE; } -/* Force small values to zero. We do this to avoid having sin(360deg) - * evaluate to a tiny but nonzero value. - */ -static double FlushToZero(double aVal) +template +T InterpolateNumerically(const T& aOne, const T& aTwo, double aCoeff) { - if (-FLT_EPSILON < aVal && aVal < FLT_EPSILON) - return 0.0f; - else - return aVal; -} - -/* Computes tan(aTheta). For values of aTheta such that tan(aTheta) is - * undefined or very large, SafeTangent returns a manageably large value - * of the correct sign. - */ -static double SafeTangent(double aTheta) -{ - const double kEpsilon = 0.0001; - - /* tan(theta) = sin(theta)/cos(theta); problems arise when - * cos(theta) is too close to zero. Limit cos(theta) to the - * range [-1, -epsilon] U [epsilon, 1]. - */ - double sinTheta = sin(aTheta); - double cosTheta = cos(aTheta); - - if (cosTheta >= 0 && cosTheta < kEpsilon) - cosTheta = kEpsilon; - else if (cosTheta < 0 && cosTheta >= -kEpsilon) - cosTheta = -kEpsilon; - - return FlushToZero(sinTheta / cosTheta); + return aOne + (aTwo - aOne) * aCoeff; } -/* static */ gfxMatrix -nsStyleAnimation::InterpolateTransformMatrix(const gfxMatrix &aMatrix1, - double aCoeff1, - const gfxMatrix &aMatrix2, - double aCoeff2) +/* static */ gfx3DMatrix +nsStyleAnimation::InterpolateTransformMatrix(const gfx3DMatrix &aMatrix1, + const gfx3DMatrix &aMatrix2, + double aProgress) { - float rotate1, XYshear1, scaleX1, scaleY1; - DecomposeMatrix(aMatrix1, rotate1, XYshear1, scaleX1, scaleY1); - float rotate2, XYshear2, scaleX2, scaleY2; - DecomposeMatrix(aMatrix2, rotate2, XYshear2, scaleX2, scaleY2); + // Decompose both matrices - float rotate = rotate1 * aCoeff1 + rotate2 * aCoeff2; + // TODO: What do we do if one of these returns PR_FALSE (singular matrix) - float skewX = atanf(XYshear1) * aCoeff1 + atanf(XYshear2) * aCoeff2; + gfxPoint3D scale1(1, 1, 1), translate1; + gfxPointH3D perspective1(0, 0, 0, 1); + gfxQuaternion rotate1; + float shear1[3] = { 0.0f, 0.0f, 0.0f}; - // Handle scale, and the two matrix components where identity is 1, by - // subtracting 1, multiplying by the coefficients, and then adding 1 - // back. This gets the right AddWeighted behavior and gets us the - // interpolation-against-identity behavior for free. - float scaleX = - ((scaleX1 - 1.0f) * aCoeff1 + (scaleX2 - 1.0f) * aCoeff2) + 1.0f; - float scaleY = - ((scaleY1 - 1.0f) * aCoeff1 + (scaleY2 - 1.0f) * aCoeff2) + 1.0f; + gfxPoint3D scale2(1, 1, 1), translate2; + gfxPointH3D perspective2(0, 0, 0, 1); + gfxQuaternion rotate2; + float shear2[3] = { 0.0f, 0.0f, 0.0f}; - gfxMatrix result; + gfxMatrix matrix2d1, matrix2d2; + if (aMatrix1.Is2D(&matrix2d1) && aMatrix2.Is2D(&matrix2d2)) { + Decompose2DMatrix(matrix2d1, scale1, shear1, rotate1, translate1); + Decompose2DMatrix(matrix2d2, scale2, shear2, rotate2, translate2); + } else { + Decompose3DMatrix(aMatrix1, scale1, shear1, + rotate1, translate1, perspective1); + Decompose3DMatrix(aMatrix2, scale2, shear2, + rotate2, translate2, perspective2); + } - gfxMatrix skew; - skew.xy = SafeTangent(skewX); - result.Translate(gfxPoint(aMatrix1.x0 * aCoeff1 + aMatrix2.x0 * aCoeff2, - aMatrix1.y0 * aCoeff1 + aMatrix2.y0 * aCoeff2)); - result.Rotate(rotate); - result.PreMultiply(skew); - result.Scale(scaleX, scaleY); + // Interpolate each of the pieces + gfx3DMatrix result; + + gfxPointH3D perspective = + InterpolateNumerically(perspective1, perspective2, aProgress); + result.SetTransposedVector(3, perspective); + + gfxPoint3D translate = + InterpolateNumerically(translate1, translate2, aProgress); + result.Translate(translate); + + gfxQuaternion q3 = rotate1.Slerp(rotate2, aProgress); + gfx3DMatrix 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? + float yzshear = + InterpolateNumerically(shear1[YZSHEAR], shear2[YZSHEAR], aProgress); + if (yzshear != 0.0) { + result.SkewYZ(yzshear); + } + + float xzshear = + InterpolateNumerically(shear1[XZSHEAR], shear2[XZSHEAR], aProgress); + if (xzshear != 0.0) { + result.SkewXZ(xzshear); + } + + float xyshear = + InterpolateNumerically(shear1[XYSHEAR], shear2[XYSHEAR], aProgress); + if (xyshear != 0.0) { + result.SkewXY(xyshear); + } + + gfxPoint3D scale = + InterpolateNumerically(scale1, scale2, aProgress); + if (scale != gfxPoint3D(1.0, 1.0, 1.0)) { + result.Scale(scale.x, scale.y, scale.z); + } return result; } @@ -1196,15 +1337,36 @@ AddDifferentTransformLists(const nsCSSValueList* aList1, double aCoeff1, nsRefPtr arr; arr = AppendTransformFunction(eCSSKeyword_interpolatematrix, resultTail); + + // FIXME: We should change the other transform code to also only + // take a single progress value, as having values that don't + // sum to 1 doesn't make sense for these. + if (aList1 == aList2) { + arr->Item(1).Reset(); + } else { + aList1->CloneInto(arr->Item(1).SetListValue()); + } - arr->Item(1).SetPercentValue(aCoeff1); - aList1->CloneInto(arr->Item(2).SetListValue()); + aList2->CloneInto(arr->Item(2).SetListValue()); arr->Item(3).SetPercentValue(aCoeff2); - aList2->CloneInto(arr->Item(4).SetListValue()); return result.forget(); } +static PRBool +TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2) +{ + if (func1 == func2) { + return PR_TRUE; + } + + if (func1 == eCSSKeyword_rotatez && func2 == eCSSKeyword_rotate || + func1 == eCSSKeyword_rotate && func2 == eCSSKeyword_rotatez) { + return PR_TRUE; + } + return PR_FALSE; +} + static nsCSSValueList* AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, const nsCSSValueList* aList2, double aCoeff2) @@ -1215,13 +1377,17 @@ AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, do { const nsCSSValue::Array *a1 = aList1->mValue.GetArrayValue(), *a2 = aList2->mValue.GetArrayValue(); - NS_ABORT_IF_FALSE(nsStyleTransformMatrix::TransformFunctionOf(a1) == - nsStyleTransformMatrix::TransformFunctionOf(a2), + NS_ABORT_IF_FALSE(TransformFunctionsMatch(nsStyleTransformMatrix::TransformFunctionOf(a1), + nsStyleTransformMatrix::TransformFunctionOf(a2)), "transform function mismatch"); nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1); nsRefPtr arr; - if (tfunc != eCSSKeyword_matrix && tfunc != eCSSKeyword_interpolatematrix) { + if (tfunc != eCSSKeyword_matrix && + tfunc != eCSSKeyword_matrix3d && + tfunc != eCSSKeyword_interpolatematrix && + tfunc != eCSSKeyword_rotate3d && + tfunc != eCSSKeyword_perspective) { arr = AppendTransformFunction(tfunc, resultTail); } @@ -1250,13 +1416,25 @@ AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, break; } case eCSSKeyword_translatex: - case eCSSKeyword_translatey: { + case eCSSKeyword_translatey: + case eCSSKeyword_translatez: { NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count"); NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count"); AddTransformTranslate(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2, arr->Item(1)); break; } + case eCSSKeyword_translate3d: { + NS_ABORT_IF_FALSE(a1->Count() == 4, "unexpected count"); + NS_ABORT_IF_FALSE(a2->Count() == 4, "unexpected count"); + AddTransformTranslate(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2, + arr->Item(1)); + AddTransformTranslate(a1->Item(2), aCoeff1, a2->Item(2), aCoeff2, + arr->Item(2)); + AddTransformTranslate(a1->Item(3), aCoeff1, a2->Item(3), aCoeff2, + arr->Item(3)); + break; + } case eCSSKeyword_scale: { NS_ABORT_IF_FALSE(a1->Count() == 2 || a1->Count() == 3, "unexpected count"); @@ -1281,7 +1459,8 @@ AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, break; } case eCSSKeyword_scalex: - case eCSSKeyword_scaley: { + case eCSSKeyword_scaley: + case eCSSKeyword_scalez: { NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count"); NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count"); @@ -1290,6 +1469,19 @@ AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, break; } + case eCSSKeyword_scale3d: { + NS_ABORT_IF_FALSE(a1->Count() == 4, "unexpected count"); + NS_ABORT_IF_FALSE(a2->Count() == 4, "unexpected count"); + + AddTransformScale(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2, + arr->Item(1)); + AddTransformScale(a1->Item(2), aCoeff1, a2->Item(2), aCoeff2, + arr->Item(2)); + AddTransformScale(a1->Item(3), aCoeff1, a2->Item(3), aCoeff2, + arr->Item(3)); + + break; + } // It would probably be nicer to animate skew in tangent space // rather than angle space. However, it's easy to specify // skews with infinite tangents, and behavior changes pretty @@ -1318,7 +1510,10 @@ AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, } case eCSSKeyword_skewx: case eCSSKeyword_skewy: - case eCSSKeyword_rotate: { + case eCSSKeyword_rotate: + case eCSSKeyword_rotatex: + case eCSSKeyword_rotatey: + case eCSSKeyword_rotatez: { NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count"); NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count"); @@ -1328,18 +1523,25 @@ AddTransformLists(const nsCSSValueList* aList1, double aCoeff1, break; } case eCSSKeyword_matrix: - case eCSSKeyword_interpolatematrix: { + case eCSSKeyword_matrix3d: + case eCSSKeyword_interpolatematrix: + case eCSSKeyword_rotate3d: + case eCSSKeyword_perspective: { // FIXME: If the matrix contains only numbers then we could decompose - // here. We can't do this for matrix3d though, so it's probably - // best to stay consistent. + // here. // Construct temporary lists with only this item in them. nsCSSValueList tempList1, tempList2; tempList1.mValue = aList1->mValue; tempList2.mValue = aList2->mValue; - *resultTail = - AddDifferentTransformLists(&tempList1, aCoeff1, &tempList2, aCoeff2); + if (aList1 == aList2) { + *resultTail = + AddDifferentTransformLists(&tempList1, aCoeff1, &tempList1, aCoeff2); + } else { + *resultTail = + AddDifferentTransformLists(&tempList1, aCoeff1, &tempList2, aCoeff2); + } while ((*resultTail)->mNext) { resultTail = &(*resultTail)->mNext; @@ -1774,11 +1976,11 @@ nsStyleAnimation::AddWeighted(nsCSSProperty aProperty, result->mValue.SetNoneValue(); } } else { - result = AddTransformLists(list2, aCoeff2, list2, 0); + result = AddTransformLists(list2, 0, list2, aCoeff2); } } else { if (list2->mValue.GetUnit() == eCSSUnit_None) { - result = AddTransformLists(list1, aCoeff1, list1, 0); + result = AddTransformLists(list1, 0, list1, aCoeff1); } else { PRBool match = PR_TRUE; @@ -1789,7 +1991,8 @@ nsStyleAnimation::AddWeighted(nsCSSProperty aProperty, item1->mValue.GetArrayValue()); nsCSSKeyword func2 = nsStyleTransformMatrix::TransformFunctionOf( item2->mValue.GetArrayValue()); - if (func1 != func2) { + + if (!TransformFunctionsMatch(func1, func2)) { break; } diff --git a/layout/style/nsStyleAnimation.h b/layout/style/nsStyleAnimation.h index a09b254d268..aec686a8318 100644 --- a/layout/style/nsStyleAnimation.h +++ b/layout/style/nsStyleAnimation.h @@ -58,7 +58,7 @@ struct nsCSSValuePair; struct nsCSSValueTriplet; struct nsCSSValuePairList; struct nsCSSRect; -struct gfxMatrix; +class gfx3DMatrix; namespace mozilla { namespace dom { @@ -233,12 +233,12 @@ public: * Interpolates between 2 matrices by decomposing them. * * @param aMatrix1 First matrix, using CSS pixel units. - * @param aCoeff1 Interpolation value in the range [0.0, 1.0] * @param aMatrix2 Second matrix, using CSS pixel units. - * @param aCoeff2 Interpolation value in the range [0.0, 1.0] + * @param aProgress Interpolation value in the range [0.0, 1.0] */ - static gfxMatrix InterpolateTransformMatrix(const gfxMatrix &aMatrix1, double aCoeff1, - const gfxMatrix &aMatrix2, double aCoeff2); + static gfx3DMatrix InterpolateTransformMatrix(const gfx3DMatrix &aMatrix1, + const gfx3DMatrix &aMatrix2, + double aProgress); /** * The types and values for the values that we extract and animate. diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp index df13468b209..7b1eeb2c4b1 100644 --- a/layout/style/nsStyleTransformMatrix.cpp +++ b/layout/style/nsStyleTransformMatrix.cpp @@ -206,34 +206,24 @@ nsStyleTransformMatrix::ProcessInterpolateMatrix(const nsCSSValue::Array* aData, PRBool& aCanStoreInRuleTree, nsRect& aBounds, float aAppUnitsPerMatrixUnit) { - NS_PRECONDITION(aData->Count() == 5, "Invalid array!"); + NS_PRECONDITION(aData->Count() == 4, "Invalid array!"); - double coeff1 = aData->Item(1).GetPercentValue(); - gfx3DMatrix matrix1 = ReadTransforms(aData->Item(2).GetListValue(), - aContext, aPresContext, - aCanStoreInRuleTree, - aBounds, aAppUnitsPerMatrixUnit); - double coeff2 = aData->Item(3).GetPercentValue(); - gfx3DMatrix matrix2 = ReadTransforms(aData->Item(4).GetListValue(), - aContext, aPresContext, - aCanStoreInRuleTree, - aBounds, aAppUnitsPerMatrixUnit); + gfx3DMatrix matrix1, matrix2; + if (aData->Item(1).GetUnit() == eCSSUnit_List) { + matrix1 = ReadTransforms(aData->Item(1).GetListValue(), + aContext, aPresContext, + aCanStoreInRuleTree, + aBounds, aAppUnitsPerMatrixUnit); + } + if (aData->Item(2).GetUnit() == eCSSUnit_List) { + matrix2 = ReadTransforms(aData->Item(2).GetListValue(), + aContext, aPresContext, + aCanStoreInRuleTree, + aBounds, aAppUnitsPerMatrixUnit); + } + double progress = aData->Item(3).GetPercentValue(); - gfxMatrix matrix2d1, matrix2d2; -#ifdef DEBUG - PRBool is2d = -#endif - matrix1.Is2D(&matrix2d1); - NS_ABORT_IF_FALSE(is2d, "Can't do animations with 3d transforms!"); -#ifdef DEBUG - is2d = -#endif - matrix2.Is2D(&matrix2d2); - NS_ABORT_IF_FALSE(is2d, "Can't do animations with 3d transforms!"); - - return gfx3DMatrix::From2D( - nsStyleAnimation::InterpolateTransformMatrix(matrix2d1, coeff1, - matrix2d2, coeff2)); + return nsStyleAnimation::InterpolateTransformMatrix(matrix1, matrix2, progress); } /* Helper function to process a translatex function. */ diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html index e92cdc9bd3e..52686aad253 100644 --- a/layout/style/test/test_transitions_per_property.html +++ b/layout/style/test/test_transitions_per_property.html @@ -1425,20 +1425,23 @@ function test_transform_transition(prop) { // matrix : skewX { start: 'matrix(1, 0, 3, 1, 0px, 0px)', end: 'none', - expected: 'matrix(1, 0, ' + Math.tan(Math.atan(3) * 0.75) + ', 1, 0px, 0px)', + expected: 'matrix(1, 0, ' + 3 * 0.75 + ', 1, 0px, 0px)', round_error_ok: true }, { start: 'skewX(0)', end: 'skewX(-45deg) translate(0)', - expected: 'matrix(1, 0, -0.198912367, 1, 0px, 0px)', + expected: 'matrix(1, 0, -0.25, 1, 0px, 0px)', round_error_ok: true }, // matrix : rotate { start: 'rotate(-30deg)', end: 'matrix(0, 1, -1, 0, 0px, 0px)', - expected: 'matrix(1, 0, 0, 1, 0px, 0px)' }, + expected: 'matrix(1, 0, 0, 1, 0px, 0px)', + round_error_ok: true }, { start: 'rotate(-30deg) translateX(0)', end: 'translateX(0) rotate(-90deg)', - expected: c('rotate(-45deg)') }, + expected: c('rotate(-45deg)'), + round_error_ok: true }, // matrix decomposition of skewY { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)', - expected: c('rotate(30deg) skewX(30deg) scale(2, 0.5)'), + /* rotate(30deg) skewX(60deg)/2 scale(2, 0.5) */ + expected: c('rotate(30deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'), round_error_ok: true }, // matrix decomposition @@ -1493,42 +1496,42 @@ function test_transform_transition(prop) { { start: 'none', end: 'matrix(1, 0, 1.5, 1, 0pt, 0pt)', /* skewX(atan(1.5)) */ - expected: 'matrix(1, 0, ' + Math.tan(Math.atan(1.5) * 0.25).toFixed(6) + ', 1, 0px, 0px)', + expected: 'matrix(1, 0, ' + 1.5 * 0.25 + ', 1, 0px, 0px)', round_error_ok: true }, { start: 'none', end: 'matrix(-1, 0, 2, -1, 0pt, 0pt)', /* rotate(180deg) skewX(atan(-2)) */ - expected: c('rotate(45deg) matrix(1, 0, ' + Math.tan(Math.atan(-2) * 0.25) + ', 1, 0px, 0px)'), + expected: c('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0px, 0px)'), round_error_ok: true }, { start: 'none', end: 'matrix(0, -1, 1, -3, 0pt, 0pt)', /* rotate(-90deg) skewX(atan(3)) */ - expected: c('rotate(-22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(3) * 0.25) + ', 1, 0px, 0px)'), + expected: c('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0px, 0px)'), round_error_ok: true }, { start: 'none', end: 'matrix(0, 1, -1, 4, 0pt, 0pt)', /* rotate(90deg) skewX(atan(4)) */ - expected: c('rotate(22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(4) * 0.25) + ', 1, 0px, 0px)'), + expected: c('rotate(22.5deg) matrix(1, 0, ' + 4 * 0.25 + ', 1, 0px, 0px)'), round_error_ok: true }, // and then four with negative determinants { start: 'none', end: 'matrix(1, 0, 1, -1, 0pt, 0pt)', /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */ - expected: c('rotate(-45deg) matrix(1, 0, ' + Math.tan(Math.atan(-1) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)'), + expected: c('rotate(-45deg) matrix(1, 0, ' + -1 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)'), round_error_ok: true }, { start: 'none', end: 'matrix(-1, 0, -1, 1, 0pt, 0pt)', /* skewX(atan(-1)) scaleX(-1) */ - expected: c('matrix(1, 0, ' + Math.tan(Math.atan(-1) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)') }, + expected: c('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)') }, { start: 'none', end: 'matrix(0, 1, 1, -2, 0pt, 0pt)', /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */ - expected: c('rotate(-22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(2) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)'), + expected: c('rotate(-22.5deg) matrix(1, 0, ' + 2 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)'), round_error_ok: true }, { start: 'none', end: 'matrix(0, -1, -1, 0.5, 0pt, 0pt)', /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */ - expected: c('rotate(22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(0.5) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)'), + expected: c('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)'), round_error_ok: true }, // lists vs. matrix decomposition @@ -1540,14 +1543,14 @@ function test_transform_transition(prop) { expected_uncomputed: 'skewY(22.5deg) rotate(90deg)' }, { start: 'skewY(45deg) rotate(90deg) translate(0)', end: 'skewY(-45deg) rotate(90deg)', - expected: c('rotate(90deg) skewX(-22.5deg)'), + expected: 'matrix(0, 1, -1, -0.5, 0px, 0px)', round_error_ok: true }, { start: 'skewX(45deg) rotate(90deg)', end: 'skewX(-45deg) rotate(90deg)', expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' }, { start: 'skewX(-60deg) rotate(90deg) translate(0)', end: 'skewX(60deg) rotate(90deg)', - expected: c('rotate(120deg) skewX(30deg) scale(2, 0.5)'), + expected: c('rotate(120deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'), round_error_ok: true }, ];