skia2/tests/MatrixTest.cpp
egdaniel@google.com 259fbaf7a4 Add homogeneous point mapping to Matrix
Adds mapping of homogeneous points (points with three scalar components,
where the last component is not 1). Includes fix for tests when
running on 32 bit debug builds

BUG=
R=bsalomon@google.com

Review URL: https://codereview.chromium.org/22816005

git-svn-id: http://skia.googlecode.com/svn/trunk@10755 2bbb7eff-a529-9590-31e7-b0007b416f81
2013-08-15 21:12:11 +00:00

859 lines
34 KiB
C++

/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "Test.h"
#include "SkMath.h"
#include "SkMatrix.h"
#include "SkMatrixUtils.h"
#include "SkRandom.h"
static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
// Note that we get more compounded error for multiple operations when
// SK_SCALAR_IS_FIXED.
#ifdef SK_SCALAR_IS_FLOAT
const SkScalar tolerance = SK_Scalar1 / 200000;
#else
const SkScalar tolerance = SK_Scalar1 / 1024;
#endif
return SkScalarAbs(a - b) <= tolerance;
}
static bool nearly_equal(const SkMatrix& a, const SkMatrix& b) {
for (int i = 0; i < 9; i++) {
if (!nearly_equal_scalar(a[i], b[i])) {
printf("not equal %g %g\n", (float)a[i], (float)b[i]);
return false;
}
}
return true;
}
static bool are_equal(skiatest::Reporter* reporter,
const SkMatrix& a,
const SkMatrix& b) {
bool equal = a == b;
bool cheapEqual = a.cheapEqualTo(b);
if (equal != cheapEqual) {
#ifdef SK_SCALAR_IS_FLOAT
if (equal) {
bool foundZeroSignDiff = false;
for (int i = 0; i < 9; ++i) {
float aVal = a.get(i);
float bVal = b.get(i);
int aValI = *SkTCast<int*>(&aVal);
int bValI = *SkTCast<int*>(&bVal);
if (0 == aVal && 0 == bVal && aValI != bValI) {
foundZeroSignDiff = true;
} else {
REPORTER_ASSERT(reporter, aVal == bVal && aValI == aValI);
}
}
REPORTER_ASSERT(reporter, foundZeroSignDiff);
} else {
bool foundNaN = false;
for (int i = 0; i < 9; ++i) {
float aVal = a.get(i);
float bVal = b.get(i);
int aValI = *SkTCast<int*>(&aVal);
int bValI = *SkTCast<int*>(&bVal);
if (sk_float_isnan(aVal) && aValI == bValI) {
foundNaN = true;
} else {
REPORTER_ASSERT(reporter, aVal == bVal && aValI == bValI);
}
}
REPORTER_ASSERT(reporter, foundNaN);
}
#else
REPORTER_ASSERT(reporter, false);
#endif
}
return equal;
}
static bool is_identity(const SkMatrix& m) {
SkMatrix identity;
identity.reset();
return nearly_equal(m, identity);
}
static void test_matrix_recttorect(skiatest::Reporter* reporter) {
SkRect src, dst;
SkMatrix matrix;
src.set(0, 0, SK_Scalar1*10, SK_Scalar1*10);
dst = src;
matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
REPORTER_ASSERT(reporter, SkMatrix::kIdentity_Mask == matrix.getType());
REPORTER_ASSERT(reporter, matrix.rectStaysRect());
dst.offset(SK_Scalar1, SK_Scalar1);
matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
REPORTER_ASSERT(reporter, SkMatrix::kTranslate_Mask == matrix.getType());
REPORTER_ASSERT(reporter, matrix.rectStaysRect());
dst.fRight += SK_Scalar1;
matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
REPORTER_ASSERT(reporter,
(SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask) == matrix.getType());
REPORTER_ASSERT(reporter, matrix.rectStaysRect());
dst = src;
dst.fRight = src.fRight * 2;
matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
REPORTER_ASSERT(reporter, SkMatrix::kScale_Mask == matrix.getType());
REPORTER_ASSERT(reporter, matrix.rectStaysRect());
}
static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
// add 100 in case we have a bug, I don't want to kill my stack in the test
char buffer[SkMatrix::kMaxFlattenSize + 100];
uint32_t size1 = m.writeToMemory(NULL);
uint32_t size2 = m.writeToMemory(buffer);
REPORTER_ASSERT(reporter, size1 == size2);
REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
SkMatrix m2;
uint32_t size3 = m2.readFromMemory(buffer);
REPORTER_ASSERT(reporter, size1 == size3);
REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
char buffer2[SkMatrix::kMaxFlattenSize + 100];
size3 = m2.writeToMemory(buffer2);
REPORTER_ASSERT(reporter, size1 == size3);
REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
}
static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
SkMatrix identity;
identity.reset();
REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
SkMatrix scale;
scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
SkMatrix rot90Scale;
rot90Scale.setRotate(90 * SK_Scalar1);
rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
SkMatrix rotate;
rotate.setRotate(128 * SK_Scalar1);
REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
SkMatrix translate;
translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
SkMatrix perspX;
perspX.reset();
perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
SkMatrix perspY;
perspY.reset();
perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
SkMatrix baseMats[] = {scale, rot90Scale, rotate,
translate, perspX, perspY};
SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
mats[i] = baseMats[i];
bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
REPORTER_ASSERT(reporter, invertable);
}
SkMWCRandom rand;
for (int m = 0; m < 1000; ++m) {
SkMatrix mat;
mat.reset();
for (int i = 0; i < 4; ++i) {
int x = rand.nextU() % SK_ARRAY_COUNT(mats);
mat.postConcat(mats[x]);
}
SkScalar stretch = mat.getMaxStretch();
if ((stretch < 0) != mat.hasPerspective()) {
stretch = mat.getMaxStretch();
}
REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
if (mat.hasPerspective()) {
m -= 1; // try another non-persp matrix
continue;
}
// test a bunch of vectors. None should be scaled by more than stretch
// (modulo some error) and we should find a vector that is scaled by
// almost stretch.
static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
SkScalar max = 0;
SkVector vectors[1000];
for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
vectors[i].fX = rand.nextSScalar1();
vectors[i].fY = rand.nextSScalar1();
if (!vectors[i].normalize()) {
i -= 1;
continue;
}
}
mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
SkScalar d = vectors[i].length();
REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
if (max < d) {
max = d;
}
}
REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
}
}
static void test_matrix_is_similarity(skiatest::Reporter* reporter) {
SkMatrix mat;
// identity
mat.setIdentity();
REPORTER_ASSERT(reporter, mat.isSimilarity());
// translation only
mat.reset();
mat.setTranslate(SkIntToScalar(100), SkIntToScalar(100));
REPORTER_ASSERT(reporter, mat.isSimilarity());
// scale with same size
mat.reset();
mat.setScale(SkIntToScalar(15), SkIntToScalar(15));
REPORTER_ASSERT(reporter, mat.isSimilarity());
// scale with one negative
mat.reset();
mat.setScale(SkIntToScalar(-15), SkIntToScalar(15));
REPORTER_ASSERT(reporter, mat.isSimilarity());
// scale with different size
mat.reset();
mat.setScale(SkIntToScalar(15), SkIntToScalar(20));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// scale with same size at a pivot point
mat.reset();
mat.setScale(SkIntToScalar(15), SkIntToScalar(15),
SkIntToScalar(2), SkIntToScalar(2));
REPORTER_ASSERT(reporter, mat.isSimilarity());
// scale with different size at a pivot point
mat.reset();
mat.setScale(SkIntToScalar(15), SkIntToScalar(20),
SkIntToScalar(2), SkIntToScalar(2));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// skew with same size
mat.reset();
mat.setSkew(SkIntToScalar(15), SkIntToScalar(15));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// skew with different size
mat.reset();
mat.setSkew(SkIntToScalar(15), SkIntToScalar(20));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// skew with same size at a pivot point
mat.reset();
mat.setSkew(SkIntToScalar(15), SkIntToScalar(15),
SkIntToScalar(2), SkIntToScalar(2));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// skew with different size at a pivot point
mat.reset();
mat.setSkew(SkIntToScalar(15), SkIntToScalar(20),
SkIntToScalar(2), SkIntToScalar(2));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// perspective x
mat.reset();
mat.setPerspX(SkScalarToPersp(SK_Scalar1 / 2));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// perspective y
mat.reset();
mat.setPerspY(SkScalarToPersp(SK_Scalar1 / 2));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
#ifdef SK_SCALAR_IS_FLOAT
/* We bypass the following tests for SK_SCALAR_IS_FIXED build.
* The long discussion can be found in this issue:
* http://codereview.appspot.com/5999050/
* In short, we haven't found a perfect way to fix the precision
* issue, i.e. the way we use tolerance in isSimilarityTransformation
* is incorrect. The situation becomes worse in fixed build, so
* we disabled rotation related tests for fixed build.
*/
// rotate
for (int angle = 0; angle < 360; ++angle) {
mat.reset();
mat.setRotate(SkIntToScalar(angle));
REPORTER_ASSERT(reporter, mat.isSimilarity());
}
// see if there are any accumulated precision issues
mat.reset();
for (int i = 1; i < 360; i++) {
mat.postRotate(SkIntToScalar(1));
}
REPORTER_ASSERT(reporter, mat.isSimilarity());
// rotate + translate
mat.reset();
mat.setRotate(SkIntToScalar(30));
mat.postTranslate(SkIntToScalar(10), SkIntToScalar(20));
REPORTER_ASSERT(reporter, mat.isSimilarity());
// rotate + uniform scale
mat.reset();
mat.setRotate(SkIntToScalar(30));
mat.postScale(SkIntToScalar(2), SkIntToScalar(2));
REPORTER_ASSERT(reporter, mat.isSimilarity());
// rotate + non-uniform scale
mat.reset();
mat.setRotate(SkIntToScalar(30));
mat.postScale(SkIntToScalar(3), SkIntToScalar(2));
REPORTER_ASSERT(reporter, !mat.isSimilarity());
#endif
// all zero
mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, 0);
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// all zero except perspective
mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, SK_Scalar1);
REPORTER_ASSERT(reporter, !mat.isSimilarity());
// scales zero, only skews
mat.setAll(0, SK_Scalar1, 0,
SK_Scalar1, 0, 0,
0, 0, SkMatrix::I()[8]);
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.f;
mat[SkMatrix::kMPersp1] = 4.f;
mat[SkMatrix::kMPersp2] = 1872.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));
// 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));
}
// For test_matrix_homogeneous, below.
static bool scalar_array_nearly_equal_relative(const SkScalar a[], const SkScalar b[], int count) {
for (int i = 0; i < count; ++i) {
if (!scalar_nearly_equal_relative(a[i], b[i])) {
return false;
}
}
return true;
}
// For test_matrix_homogeneous, below.
// Maps a single triple in src using m and compares results to those in dst
static bool naive_homogeneous_mapping(const SkMatrix& m, const SkScalar src[3],
const SkScalar dst[3]) {
SkScalar res[3];
SkScalar ms[9] = {m[0], m[1], m[2],
m[3], m[4], m[5],
m[6], m[7], m[8]};
res[0] = src[0] * ms[0] + src[1] * ms[1] + src[2] * ms[2];
res[1] = src[0] * ms[3] + src[1] * ms[4] + src[2] * ms[5];
res[2] = src[0] * ms[6] + src[1] * ms[7] + src[2] * ms[8];
return scalar_array_nearly_equal_relative(res, dst, 3);
}
static void test_matrix_homogeneous(skiatest::Reporter* reporter) {
SkMatrix mat;
const float kRotation0 = 15.5f;
const float kRotation1 = -50.f;
const float kScale0 = 5000.f;
const int kTripleCount = 1000;
const int kMatrixCount = 1000;
SkRandom rand;
SkScalar randTriples[3*kTripleCount];
for (int i = 0; i < 3*kTripleCount; ++i) {
randTriples[i] = rand.nextRangeF(-3000.f, 3000.f);
}
SkMatrix mats[kMatrixCount];
for (int i = 0; i < kMatrixCount; ++i) {
for (int j = 0; j < 9; ++j) {
mats[i].set(j, rand.nextRangeF(-3000.f, 3000.f));
}
}
// identity
{
mat.reset();
SkScalar dst[3*kTripleCount];
mat.mapHomogeneousPoints(dst, randTriples, kTripleCount);
REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(randTriples, dst, kTripleCount*3));
}
// zero matrix
{
mat.setAll(0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
SkScalar dst[3*kTripleCount];
mat.mapHomogeneousPoints(dst, randTriples, kTripleCount);
SkScalar zeros[3] = {0.f, 0.f, 0.f};
for (int i = 0; i < kTripleCount; ++i) {
REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(&dst[i*3], zeros, 3));
}
}
// zero point
{
SkScalar zeros[3] = {0.f, 0.f, 0.f};
for (int i = 0; i < kMatrixCount; ++i) {
SkScalar dst[3];
mats[i].mapHomogeneousPoints(dst, zeros, 1);
REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(dst, zeros, 3));
}
}
// doesn't crash with null dst, src, count == 0
{
mats[0].mapHomogeneousPoints(NULL, NULL, 0);
}
// uniform scale of point
{
mat.setScale(kScale0, kScale0);
SkScalar dst[3];
SkScalar src[3] = {randTriples[0], randTriples[1], 1.f};
SkPoint pnt;
pnt.set(src[0], src[1]);
mat.mapHomogeneousPoints(dst, src, 1);
mat.mapPoints(&pnt, &pnt, 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1));
}
// rotation of point
{
mat.setRotate(kRotation0);
SkScalar dst[3];
SkScalar src[3] = {randTriples[0], randTriples[1], 1.f};
SkPoint pnt;
pnt.set(src[0], src[1]);
mat.mapHomogeneousPoints(dst, src, 1);
mat.mapPoints(&pnt, &pnt, 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1));
}
// rotation, scale, rotation of point
{
mat.setRotate(kRotation1);
mat.postScale(kScale0, kScale0);
mat.postRotate(kRotation0);
SkScalar dst[3];
SkScalar src[3] = {randTriples[0], randTriples[1], 1.f};
SkPoint pnt;
pnt.set(src[0], src[1]);
mat.mapHomogeneousPoints(dst, src, 1);
mat.mapPoints(&pnt, &pnt, 1);
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1));
}
// compare with naive approach
{
for (int i = 0; i < kMatrixCount; ++i) {
for (int j = 0; j < kTripleCount; ++j) {
SkScalar dst[3];
mats[i].mapHomogeneousPoints(dst, &randTriples[j*3], 1);
REPORTER_ASSERT(reporter, naive_homogeneous_mapping(mats[i], &randTriples[j*3], dst));
}
}
}
}
static void TestMatrix(skiatest::Reporter* reporter) {
SkMatrix mat, inverse, iden1, iden2;
mat.reset();
mat.setTranslate(SK_Scalar1, SK_Scalar1);
REPORTER_ASSERT(reporter, mat.invert(&inverse));
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
mat.setScale(SkIntToScalar(2), SkIntToScalar(4));
REPORTER_ASSERT(reporter, mat.invert(&inverse));
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
test_flatten(reporter, mat);
mat.setScale(SK_Scalar1/2, SkIntToScalar(2));
REPORTER_ASSERT(reporter, mat.invert(&inverse));
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
test_flatten(reporter, mat);
mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
mat.postRotate(SkIntToScalar(25));
REPORTER_ASSERT(reporter, mat.invert(NULL));
REPORTER_ASSERT(reporter, mat.invert(&inverse));
iden1.setConcat(mat, inverse);
REPORTER_ASSERT(reporter, is_identity(iden1));
iden2.setConcat(inverse, mat);
REPORTER_ASSERT(reporter, is_identity(iden2));
test_flatten(reporter, mat);
test_flatten(reporter, iden2);
mat.setScale(0, SK_Scalar1);
REPORTER_ASSERT(reporter, !mat.invert(NULL));
REPORTER_ASSERT(reporter, !mat.invert(&inverse));
mat.setScale(SK_Scalar1, 0);
REPORTER_ASSERT(reporter, !mat.invert(NULL));
REPORTER_ASSERT(reporter, !mat.invert(&inverse));
// rectStaysRect test
{
static const struct {
SkScalar m00, m01, m10, m11;
bool mStaysRect;
}
gRectStaysRectSamples[] = {
{ 0, 0, 0, 0, false },
{ 0, 0, 0, SK_Scalar1, false },
{ 0, 0, SK_Scalar1, 0, false },
{ 0, 0, SK_Scalar1, SK_Scalar1, false },
{ 0, SK_Scalar1, 0, 0, false },
{ 0, SK_Scalar1, 0, SK_Scalar1, false },
{ 0, SK_Scalar1, SK_Scalar1, 0, true },
{ 0, SK_Scalar1, SK_Scalar1, SK_Scalar1, false },
{ SK_Scalar1, 0, 0, 0, false },
{ SK_Scalar1, 0, 0, SK_Scalar1, true },
{ SK_Scalar1, 0, SK_Scalar1, 0, false },
{ SK_Scalar1, 0, SK_Scalar1, SK_Scalar1, false },
{ SK_Scalar1, SK_Scalar1, 0, 0, false },
{ SK_Scalar1, SK_Scalar1, 0, SK_Scalar1, false },
{ SK_Scalar1, SK_Scalar1, SK_Scalar1, 0, false },
{ SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, false }
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
SkMatrix m;
m.reset();
m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
m.set(SkMatrix::kMSkewX, gRectStaysRectSamples[i].m01);
m.set(SkMatrix::kMSkewY, gRectStaysRectSamples[i].m10);
m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
REPORTER_ASSERT(reporter,
m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
}
}
mat.reset();
mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
mat.set(SkMatrix::kMSkewX, SkIntToScalar(2));
mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
mat.set(SkMatrix::kMSkewY, SkIntToScalar(4));
mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
SkScalar affine[6];
REPORTER_ASSERT(reporter, mat.asAffine(affine));
#define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
REPORTER_ASSERT(reporter, affineEqual(ScaleX));
REPORTER_ASSERT(reporter, affineEqual(SkewY));
REPORTER_ASSERT(reporter, affineEqual(SkewX));
REPORTER_ASSERT(reporter, affineEqual(ScaleY));
REPORTER_ASSERT(reporter, affineEqual(TransX));
REPORTER_ASSERT(reporter, affineEqual(TransY));
#undef affineEqual
mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
REPORTER_ASSERT(reporter, !mat.asAffine(affine));
SkMatrix mat2;
mat2.reset();
mat.reset();
SkScalar zero = 0;
mat.set(SkMatrix::kMSkewX, -zero);
REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
mat2.reset();
mat.reset();
mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
// fixed pt doesn't have the property that NaN does not equal itself.
#ifdef SK_SCALAR_IS_FIXED
REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
#else
REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
#endif
test_matrix_max_stretch(reporter);
test_matrix_is_similarity(reporter);
test_matrix_recttorect(reporter);
test_matrix_decomposition(reporter);
test_matrix_homogeneous(reporter);
}
#include "TestClassDef.h"
DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)