Index: tests/MatrixTest.cpp |
diff --git a/tests/MatrixTest.cpp b/tests/MatrixTest.cpp |
index 5dface74d82d0e79f7389212ac650dba54035bbc..f57a964dc8315d946c3abfdf258f72e84a19114b 100644 |
--- a/tests/MatrixTest.cpp |
+++ b/tests/MatrixTest.cpp |
@@ -8,6 +8,7 @@ |
#include "Test.h" |
#include "SkMath.h" |
#include "SkMatrix.h" |
+#include "SkMatrixUtils.h" |
#include "SkRandom.h" |
static bool nearly_equal_scalar(SkScalar a, SkScalar b) { |
@@ -345,6 +346,252 @@ static void test_matrix_is_similarity(skiatest::Reporter* reporter) { |
REPORTER_ASSERT(reporter, mat.isSimilarity()); |
} |
+// For test_matrix_decomposition, below. |
+static bool scalar_nearly_equal_relative(SkScalar a, SkScalar b, |
+ SkScalar tolerance = SK_ScalarNearlyZero) { |
+ // from Bruce Dawson |
+ SkScalar diff = SkScalarAbs(a - b); |
+ if (diff < tolerance) { |
+ return true; |
+ } |
+ |
+ a = SkScalarAbs(a); |
+ b = SkScalarAbs(b); |
+ SkScalar largest = (b > a) ? b : a; |
+ |
+ if (diff <= largest*tolerance) { |
+ return true; |
+ } |
+ |
+ return false; |
+} |
+ |
+static void test_matrix_decomposition(skiatest::Reporter* reporter) { |
+ SkMatrix mat; |
+ SkScalar rotation0, scaleX, scaleY, rotation1; |
+ |
+ const float kRotation0 = 15.5f; |
+ const float kRotation1 = -50.f; |
+ const float kScale0 = 5000.f; |
+ const float kScale1 = 0.001f; |
+ |
+ // identity |
+ mat.reset(); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, SK_Scalar1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, SK_Scalar1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ // make sure it doesn't crash if we pass in NULLs |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, NULL, NULL, NULL, NULL)); |
+ |
+ // rotation only |
+ mat.setRotate(kRotation0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation0))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, SK_Scalar1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, SK_Scalar1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // uniform scale only |
+ mat.setScale(kScale0, kScale0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // anisotropic scale only |
+ mat.setScale(kScale1, kScale0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // rotation then uniform scale |
+ mat.setRotate(kRotation1); |
+ mat.postScale(kScale0, kScale0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation1))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // uniform scale then rotation |
+ mat.setScale(kScale0, kScale0); |
+ mat.postRotate(kRotation1); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation1))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // rotation then uniform scale+reflection |
+ mat.setRotate(kRotation0); |
+ mat.postScale(kScale1, -kScale1); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation0))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, -kScale1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // uniform scale+reflection, then rotate |
+ mat.setScale(kScale0, -kScale0); |
+ mat.postRotate(kRotation1); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(-kRotation1))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, -kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // rotation then anisotropic scale |
+ mat.setRotate(kRotation1); |
+ mat.postScale(kScale1, kScale0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, SkDegreesToRadians(kRotation1))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // anisotropic scale then rotation |
+ mat.setScale(kScale1, kScale0); |
+ mat.postRotate(kRotation0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation1, SkDegreesToRadians(kRotation0))); |
+ |
+ // rotation, uniform scale, then different rotation |
+ mat.setRotate(kRotation1); |
+ mat.postScale(kScale0, kScale0); |
+ mat.postRotate(kRotation0); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(rotation0, |
+ SkDegreesToRadians(kRotation0 + kRotation1))); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleX, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(scaleY, kScale0)); |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(rotation1)); |
+ |
+ // rotation, anisotropic scale, then different rotation |
+ mat.setRotate(kRotation0); |
+ mat.postScale(kScale1, kScale0); |
+ mat.postRotate(kRotation1); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ // Because of the shear/skew we won't get the same results, so we need to multiply it out. |
+ // Generating the matrices requires doing a radian-to-degree calculation, then degree-to-radian |
+ // calculation (in setRotate()), which adds error, so this just computes the matrix elements |
+ // directly. |
+ SkScalar c0; |
+ SkScalar s0 = SkScalarSinCos(rotation0, &c0); |
+ SkScalar c1; |
+ SkScalar s1 = SkScalarSinCos(rotation1, &c1); |
+ // We do a relative check here because large scale factors cause problems with an absolute check |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], |
+ scaleX*c0*c1 - scaleY*s0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], |
+ -scaleX*s0*c1 - scaleY*c0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], |
+ scaleX*c0*s1 + scaleY*s0*c1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], |
+ -scaleX*s0*s1 + scaleY*c0*c1)); |
+ |
+ // try some random matrices |
+ SkMWCRandom rand; |
+ for (int m = 0; m < 1000; ++m) { |
+ SkScalar rot0 = rand.nextRangeF(-SK_ScalarPI, SK_ScalarPI); |
+ SkScalar sx = rand.nextRangeF(-3000.f, 3000.f); |
+ SkScalar sy = rand.nextRangeF(-3000.f, 3000.f); |
+ SkScalar rot1 = rand.nextRangeF(-SK_ScalarPI, SK_ScalarPI); |
+ mat.setRotate(rot0); |
+ mat.postScale(sx, sy); |
+ mat.postRotate(rot1); |
+ |
+ if (SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)) { |
+ SkScalar c0; |
+ SkScalar s0 = SkScalarSinCos(rotation0, &c0); |
+ SkScalar c1; |
+ SkScalar s1 = SkScalarSinCos(rotation1, &c1); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], |
+ scaleX*c0*c1 - scaleY*s0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], |
+ -scaleX*s0*c1 - scaleY*c0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], |
+ scaleX*c0*s1 + scaleY*s0*c1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], |
+ -scaleX*s0*s1 + scaleY*c0*c1)); |
+ } else { |
+ // if the matrix is degenerate, the basis vectors should be near-parallel or near-zero |
+ SkScalar perpdot = mat[SkMatrix::kMScaleX]*mat[SkMatrix::kMScaleY] - |
+ mat[SkMatrix::kMSkewX]*mat[SkMatrix::kMSkewY]; |
+ REPORTER_ASSERT(reporter, SkScalarNearlyZero(perpdot)); |
+ } |
+ } |
+ |
+ // translation shouldn't affect this |
+ mat.postTranslate(-1000.f, 1000.f); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ s0 = SkScalarSinCos(rotation0, &c0); |
+ s1 = SkScalarSinCos(rotation1, &c1); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], |
+ scaleX*c0*c1 - scaleY*s0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], |
+ -scaleX*s0*c1 - scaleY*c0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], |
+ scaleX*c0*s1 + scaleY*s0*c1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], |
+ -scaleX*s0*s1 + scaleY*c0*c1)); |
+ |
+ // perspective shouldn't affect this |
+ mat[SkMatrix::kMPersp0] = 12.0; |
+ mat[SkMatrix::kMPersp1] = 4.0; |
+ mat[SkMatrix::kMPersp2] = 1872.0; |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ s0 = SkScalarSinCos(rotation0, &c0); |
+ s1 = SkScalarSinCos(rotation1, &c1); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], |
+ scaleX*c0*c1 - scaleY*s0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], |
+ -scaleX*s0*c1 - scaleY*c0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], |
+ scaleX*c0*s1 + scaleY*s0*c1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], |
+ -scaleX*s0*s1 + scaleY*c0*c1)); |
+ |
+ // rotation, anisotropic scale + reflection, then different rotation |
+ mat.setRotate(kRotation0); |
+ mat.postScale(-kScale1, kScale0); |
+ mat.postRotate(kRotation1); |
+ REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ s0 = SkScalarSinCos(rotation0, &c0); |
+ s1 = SkScalarSinCos(rotation1, &c1); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX], |
+ scaleX*c0*c1 - scaleY*s0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX], |
+ -scaleX*s0*c1 - scaleY*c0*s1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY], |
+ scaleX*c0*s1 + scaleY*s0*c1)); |
+ REPORTER_ASSERT(reporter, scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY], |
+ -scaleX*s0*s1 + scaleY*c0*c1)); |
+ |
+ // degenerate matrices |
+ // mostly zero entries |
+ mat.reset(); |
+ mat[SkMatrix::kMScaleX] = 0.f; |
+ REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ mat.reset(); |
+ mat[SkMatrix::kMScaleY] = 0.f; |
+ REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+ mat.reset(); |
+ // linearly dependent entries |
+ mat[SkMatrix::kMScaleX] = 1.f; |
+ mat[SkMatrix::kMSkewX] = 2.f; |
+ mat[SkMatrix::kMSkewY] = 4.f; |
+ mat[SkMatrix::kMScaleY] = 8.f; |
+ REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation0, &scaleX, &scaleY, &rotation1)); |
+} |
+ |
static void TestMatrix(skiatest::Reporter* reporter) { |
SkMatrix mat, inverse, iden1, iden2; |
@@ -465,6 +712,7 @@ static void TestMatrix(skiatest::Reporter* reporter) { |
test_matrix_max_stretch(reporter); |
test_matrix_is_similarity(reporter); |
test_matrix_recttorect(reporter); |
+ test_matrix_decomposition(reporter); |
} |
#include "TestClassDef.h" |