blob: c188d5542f6c9a34040213b46a82d928728c49a8 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@android.comed673312009-02-27 16:24:51 +00008#include "Test.h"
tomhudson@google.com889bd8b2011-09-27 17:38:17 +00009#include "SkMath.h"
reed@android.comed673312009-02-27 16:24:51 +000010#include "SkMatrix.h"
bsalomon@google.com38396322011-09-09 19:32:04 +000011#include "SkRandom.h"
reed@android.comed673312009-02-27 16:24:51 +000012
13static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
epoger@google.com2047f002011-05-17 17:36:59 +000014 // Note that we get more compounded error for multiple operations when
15 // SK_SCALAR_IS_FIXED.
reed@android.comed673312009-02-27 16:24:51 +000016#ifdef SK_SCALAR_IS_FLOAT
epoger@google.com2047f002011-05-17 17:36:59 +000017 const SkScalar tolerance = SK_Scalar1 / 200000;
reed@android.comed673312009-02-27 16:24:51 +000018#else
epoger@google.com2047f002011-05-17 17:36:59 +000019 const SkScalar tolerance = SK_Scalar1 / 1024;
reed@android.comed673312009-02-27 16:24:51 +000020#endif
21
22 return SkScalarAbs(a - b) <= tolerance;
23}
24
25static bool nearly_equal(const SkMatrix& a, const SkMatrix& b) {
26 for (int i = 0; i < 9; i++) {
27 if (!nearly_equal_scalar(a[i], b[i])) {
reed@android.comd4134452011-02-09 02:24:26 +000028 printf("not equal %g %g\n", (float)a[i], (float)b[i]);
reed@android.comed673312009-02-27 16:24:51 +000029 return false;
30 }
31 }
32 return true;
33}
34
bsalomon@google.com8fe84b52012-03-26 15:24:27 +000035static bool are_equal(skiatest::Reporter* reporter,
36 const SkMatrix& a,
37 const SkMatrix& b) {
38 bool equal = a == b;
39 bool cheapEqual = a.cheapEqualTo(b);
40 if (equal != cheapEqual) {
41#if SK_SCALAR_IS_FLOAT
bsalomon@google.com39d4f3a2012-03-26 17:25:45 +000042 if (equal) {
bsalomon@google.com8fe84b52012-03-26 15:24:27 +000043 bool foundZeroSignDiff = false;
44 for (int i = 0; i < 9; ++i) {
45 float aVal = a.get(i);
46 float bVal = b.get(i);
47 int aValI = *reinterpret_cast<int*>(&aVal);
48 int bValI = *reinterpret_cast<int*>(&bVal);
49 if (0 == aVal && 0 == bVal && aValI != bValI) {
50 foundZeroSignDiff = true;
51 } else {
52 REPORTER_ASSERT(reporter, aVal == bVal && aValI == aValI);
53 }
54 }
55 REPORTER_ASSERT(reporter, foundZeroSignDiff);
56 } else {
57 bool foundNaN = false;
58 for (int i = 0; i < 9; ++i) {
59 float aVal = a.get(i);
60 float bVal = b.get(i);
61 int aValI = *reinterpret_cast<int*>(&aVal);
62 int bValI = *reinterpret_cast<int*>(&bVal);
63 if (sk_float_isnan(aVal) && aValI == bValI) {
64 foundNaN = true;
65 } else {
66 REPORTER_ASSERT(reporter, aVal == bVal && aValI == bValI);
67 }
68 }
69 REPORTER_ASSERT(reporter, foundNaN);
70 }
71#else
72 REPORTER_ASSERT(reporter, false);
73#endif
74 }
75 return equal;
76}
77
reed@android.comed673312009-02-27 16:24:51 +000078static bool is_identity(const SkMatrix& m) {
79 SkMatrix identity;
reed@android.com80e39a72009-04-02 16:59:40 +000080 identity.reset();
reed@android.comed673312009-02-27 16:24:51 +000081 return nearly_equal(m, identity);
82}
83
reed@android.com4b7577b2009-06-29 16:14:41 +000084static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
85 // add 100 in case we have a bug, I don't want to kill my stack in the test
86 char buffer[SkMatrix::kMaxFlattenSize + 100];
87 uint32_t size1 = m.flatten(NULL);
88 uint32_t size2 = m.flatten(buffer);
89 REPORTER_ASSERT(reporter, size1 == size2);
90 REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
91
92 SkMatrix m2;
93 uint32_t size3 = m2.unflatten(buffer);
94 REPORTER_ASSERT(reporter, size1 == size2);
bsalomon@google.com8fe84b52012-03-26 15:24:27 +000095 REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
reed@android.com4b7577b2009-06-29 16:14:41 +000096
97 char buffer2[SkMatrix::kMaxFlattenSize + 100];
98 size3 = m2.flatten(buffer2);
99 REPORTER_ASSERT(reporter, size1 == size2);
100 REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
101}
102
bsalomon@google.com38396322011-09-09 19:32:04 +0000103void test_matrix_max_stretch(skiatest::Reporter* reporter) {
104 SkMatrix identity;
105 identity.reset();
106 REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
107
108 SkMatrix scale;
109 scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
110 REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
111
112 SkMatrix rot90Scale;
113 rot90Scale.setRotate(90 * SK_Scalar1);
114 rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
115 REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
116
117 SkMatrix rotate;
118 rotate.setRotate(128 * SK_Scalar1);
119 REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
120
121 SkMatrix translate;
122 translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
123 REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
124
125 SkMatrix perspX;
126 perspX.reset();
bungeman@google.com07faed12011-10-07 21:55:56 +0000127 perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
bsalomon@google.com38396322011-09-09 19:32:04 +0000128 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
129
130 SkMatrix perspY;
131 perspY.reset();
bungeman@google.com07faed12011-10-07 21:55:56 +0000132 perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
bsalomon@google.com38396322011-09-09 19:32:04 +0000133 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
134
135 SkMatrix baseMats[] = {scale, rot90Scale, rotate,
136 translate, perspX, perspY};
137 SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
tomhudson@google.com83a44462011-10-27 15:27:51 +0000138 for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
bsalomon@google.com38396322011-09-09 19:32:04 +0000139 mats[i] = baseMats[i];
140 bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
141 REPORTER_ASSERT(reporter, invertable);
142 }
143 SkRandom rand;
144 for (int m = 0; m < 1000; ++m) {
145 SkMatrix mat;
146 mat.reset();
147 for (int i = 0; i < 4; ++i) {
148 int x = rand.nextU() % SK_ARRAY_COUNT(mats);
149 mat.postConcat(mats[x]);
150 }
151 SkScalar stretch = mat.getMaxStretch();
152
153 if ((stretch < 0) != mat.hasPerspective()) {
154 stretch = mat.getMaxStretch();
155 }
156
157 REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
158
159 if (mat.hasPerspective()) {
160 m -= 1; // try another non-persp matrix
161 continue;
162 }
163
164 // test a bunch of vectors. None should be scaled by more than stretch
165 // (modulo some error) and we should find a vector that is scaled by
166 // almost stretch.
167 static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
168 static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
169 SkScalar max = 0;
170 SkVector vectors[1000];
tomhudson@google.com83a44462011-10-27 15:27:51 +0000171 for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
bsalomon@google.com38396322011-09-09 19:32:04 +0000172 vectors[i].fX = rand.nextSScalar1();
173 vectors[i].fY = rand.nextSScalar1();
174 if (!vectors[i].normalize()) {
175 i -= 1;
176 continue;
177 }
178 }
179 mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
tomhudson@google.com83a44462011-10-27 15:27:51 +0000180 for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
bsalomon@google.com38396322011-09-09 19:32:04 +0000181 SkScalar d = vectors[i].length();
182 REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
183 if (max < d) {
184 max = d;
185 }
186 }
187 REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
188 }
189}
190
reed@android.comed673312009-02-27 16:24:51 +0000191void TestMatrix(skiatest::Reporter* reporter) {
192 SkMatrix mat, inverse, iden1, iden2;
193
194 mat.reset();
195 mat.setTranslate(SK_Scalar1, SK_Scalar1);
reed@google.com5bfa55b2012-04-19 18:59:25 +0000196 REPORTER_ASSERT(reporter, mat.invert(&inverse));
reed@android.comed673312009-02-27 16:24:51 +0000197 iden1.setConcat(mat, inverse);
198 REPORTER_ASSERT(reporter, is_identity(iden1));
199
200 mat.setScale(SkIntToScalar(2), SkIntToScalar(2));
reed@google.com5bfa55b2012-04-19 18:59:25 +0000201 REPORTER_ASSERT(reporter, mat.invert(&inverse));
reed@android.comed673312009-02-27 16:24:51 +0000202 iden1.setConcat(mat, inverse);
203 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000204 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000205
206 mat.setScale(SK_Scalar1/2, SK_Scalar1/2);
reed@google.com5bfa55b2012-04-19 18:59:25 +0000207 REPORTER_ASSERT(reporter, mat.invert(&inverse));
reed@android.comed673312009-02-27 16:24:51 +0000208 iden1.setConcat(mat, inverse);
209 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000210 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000211
212 mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
213 mat.postRotate(SkIntToScalar(25));
214 REPORTER_ASSERT(reporter, mat.invert(NULL));
reed@google.com5bfa55b2012-04-19 18:59:25 +0000215 REPORTER_ASSERT(reporter, mat.invert(&inverse));
reed@android.comed673312009-02-27 16:24:51 +0000216 iden1.setConcat(mat, inverse);
217 REPORTER_ASSERT(reporter, is_identity(iden1));
218 iden2.setConcat(inverse, mat);
219 REPORTER_ASSERT(reporter, is_identity(iden2));
reed@android.com4b7577b2009-06-29 16:14:41 +0000220 test_flatten(reporter, mat);
221 test_flatten(reporter, iden2);
reed@android.com80e39a72009-04-02 16:59:40 +0000222
reed@android.comed673312009-02-27 16:24:51 +0000223 // rectStaysRect test
224 {
225 static const struct {
226 SkScalar m00, m01, m10, m11;
227 bool mStaysRect;
228 }
229 gRectStaysRectSamples[] = {
230 { 0, 0, 0, 0, false },
231 { 0, 0, 0, SK_Scalar1, false },
232 { 0, 0, SK_Scalar1, 0, false },
233 { 0, 0, SK_Scalar1, SK_Scalar1, false },
234 { 0, SK_Scalar1, 0, 0, false },
235 { 0, SK_Scalar1, 0, SK_Scalar1, false },
236 { 0, SK_Scalar1, SK_Scalar1, 0, true },
237 { 0, SK_Scalar1, SK_Scalar1, SK_Scalar1, false },
238 { SK_Scalar1, 0, 0, 0, false },
239 { SK_Scalar1, 0, 0, SK_Scalar1, true },
240 { SK_Scalar1, 0, SK_Scalar1, 0, false },
241 { SK_Scalar1, 0, SK_Scalar1, SK_Scalar1, false },
242 { SK_Scalar1, SK_Scalar1, 0, 0, false },
243 { SK_Scalar1, SK_Scalar1, 0, SK_Scalar1, false },
244 { SK_Scalar1, SK_Scalar1, SK_Scalar1, 0, false },
245 { SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, false }
246 };
reed@android.com80e39a72009-04-02 16:59:40 +0000247
reed@android.comed673312009-02-27 16:24:51 +0000248 for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
249 SkMatrix m;
reed@android.com80e39a72009-04-02 16:59:40 +0000250
reed@android.comed673312009-02-27 16:24:51 +0000251 m.reset();
252 m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
253 m.set(SkMatrix::kMSkewX, gRectStaysRectSamples[i].m01);
254 m.set(SkMatrix::kMSkewY, gRectStaysRectSamples[i].m10);
255 m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
256 REPORTER_ASSERT(reporter,
257 m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
258 }
259 }
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000260
bungeman@google.comba7983e2011-07-13 20:18:16 +0000261 mat.reset();
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000262 mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
263 mat.set(SkMatrix::kMSkewX, SkIntToScalar(2));
264 mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
265 mat.set(SkMatrix::kMSkewY, SkIntToScalar(4));
266 mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
267 mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000268 SkScalar affine[6];
269 REPORTER_ASSERT(reporter, mat.asAffine(affine));
270
271 #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
272 REPORTER_ASSERT(reporter, affineEqual(ScaleX));
273 REPORTER_ASSERT(reporter, affineEqual(SkewY));
274 REPORTER_ASSERT(reporter, affineEqual(SkewX));
275 REPORTER_ASSERT(reporter, affineEqual(ScaleY));
276 REPORTER_ASSERT(reporter, affineEqual(TransX));
277 REPORTER_ASSERT(reporter, affineEqual(TransY));
278 #undef affineEqual
279
bungeman@google.com07faed12011-10-07 21:55:56 +0000280 mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000281 REPORTER_ASSERT(reporter, !mat.asAffine(affine));
bsalomon@google.com38396322011-09-09 19:32:04 +0000282
bsalomon@google.com8fe84b52012-03-26 15:24:27 +0000283 SkMatrix mat2;
284 mat2.reset();
285 mat.reset();
286 SkScalar zero = 0;
287 mat.set(SkMatrix::kMSkewX, -zero);
288 REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
289
290 mat2.reset();
291 mat.reset();
292 mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
293 mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
bsalomon@google.com9ed2ecd2012-03-26 15:57:37 +0000294 // fixed pt doesn't have the property that NaN does not equal itself.
295#ifdef SK_SCALAR_IS_FIXED
296 REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
297#else
bsalomon@google.com8fe84b52012-03-26 15:24:27 +0000298 REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
bsalomon@google.com9ed2ecd2012-03-26 15:57:37 +0000299#endif
bsalomon@google.com8fe84b52012-03-26 15:24:27 +0000300
bsalomon@google.com38396322011-09-09 19:32:04 +0000301 test_matrix_max_stretch(reporter);
reed@android.comed673312009-02-27 16:24:51 +0000302}
303
reed@android.comd8730ea2009-02-27 22:06:06 +0000304#include "TestClassDef.h"
305DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)