blob: 5920eef6d813bcf5ceb9b6b3c456aa0c31defbad [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.com8fe84b52012-03-26 15:24:27 +000042 bool foundZeroSignDiff = false;
43 for (int i = 0; i < 9; ++i) {
44 float aVal = a.get(i);
45 float bVal = b.get(i);
46 int aValI = *reinterpret_cast<int*>(&aVal);
47 int bValI = *reinterpret_cast<int*>(&bVal);
48 if (0 == aVal && 0 == bVal && aValI != bValI) {
49 foundZeroSignDiff = true;
50 } else {
51 REPORTER_ASSERT(reporter, aVal == bVal && aValI == aValI);
52 }
53 }
54 REPORTER_ASSERT(reporter, foundZeroSignDiff);
55 } else {
56 bool foundNaN = false;
57 for (int i = 0; i < 9; ++i) {
58 float aVal = a.get(i);
59 float bVal = b.get(i);
60 int aValI = *reinterpret_cast<int*>(&aVal);
61 int bValI = *reinterpret_cast<int*>(&bVal);
62 if (sk_float_isnan(aVal) && aValI == bValI) {
63 foundNaN = true;
64 } else {
65 REPORTER_ASSERT(reporter, aVal == bVal && aValI == bValI);
66 }
67 }
68 REPORTER_ASSERT(reporter, foundNaN);
69 }
70#else
71 REPORTER_ASSERT(reporter, false);
72#endif
73 }
74 return equal;
75}
76
reed@android.comed673312009-02-27 16:24:51 +000077static bool is_identity(const SkMatrix& m) {
78 SkMatrix identity;
reed@android.com80e39a72009-04-02 16:59:40 +000079 identity.reset();
reed@android.comed673312009-02-27 16:24:51 +000080 return nearly_equal(m, identity);
81}
82
reed@android.com4b7577b2009-06-29 16:14:41 +000083static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
84 // add 100 in case we have a bug, I don't want to kill my stack in the test
85 char buffer[SkMatrix::kMaxFlattenSize + 100];
86 uint32_t size1 = m.flatten(NULL);
87 uint32_t size2 = m.flatten(buffer);
88 REPORTER_ASSERT(reporter, size1 == size2);
89 REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
90
91 SkMatrix m2;
92 uint32_t size3 = m2.unflatten(buffer);
93 REPORTER_ASSERT(reporter, size1 == size2);
bsalomon@google.com8fe84b52012-03-26 15:24:27 +000094 REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
reed@android.com4b7577b2009-06-29 16:14:41 +000095
96 char buffer2[SkMatrix::kMaxFlattenSize + 100];
97 size3 = m2.flatten(buffer2);
98 REPORTER_ASSERT(reporter, size1 == size2);
99 REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
100}
101
bsalomon@google.com38396322011-09-09 19:32:04 +0000102void test_matrix_max_stretch(skiatest::Reporter* reporter) {
103 SkMatrix identity;
104 identity.reset();
105 REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
106
107 SkMatrix scale;
108 scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
109 REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
110
111 SkMatrix rot90Scale;
112 rot90Scale.setRotate(90 * SK_Scalar1);
113 rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
114 REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
115
116 SkMatrix rotate;
117 rotate.setRotate(128 * SK_Scalar1);
118 REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
119
120 SkMatrix translate;
121 translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
122 REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
123
124 SkMatrix perspX;
125 perspX.reset();
bungeman@google.com07faed12011-10-07 21:55:56 +0000126 perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
bsalomon@google.com38396322011-09-09 19:32:04 +0000127 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
128
129 SkMatrix perspY;
130 perspY.reset();
bungeman@google.com07faed12011-10-07 21:55:56 +0000131 perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
bsalomon@google.com38396322011-09-09 19:32:04 +0000132 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
133
134 SkMatrix baseMats[] = {scale, rot90Scale, rotate,
135 translate, perspX, perspY};
136 SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
tomhudson@google.com83a44462011-10-27 15:27:51 +0000137 for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
bsalomon@google.com38396322011-09-09 19:32:04 +0000138 mats[i] = baseMats[i];
139 bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
140 REPORTER_ASSERT(reporter, invertable);
141 }
142 SkRandom rand;
143 for (int m = 0; m < 1000; ++m) {
144 SkMatrix mat;
145 mat.reset();
146 for (int i = 0; i < 4; ++i) {
147 int x = rand.nextU() % SK_ARRAY_COUNT(mats);
148 mat.postConcat(mats[x]);
149 }
150 SkScalar stretch = mat.getMaxStretch();
151
152 if ((stretch < 0) != mat.hasPerspective()) {
153 stretch = mat.getMaxStretch();
154 }
155
156 REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
157
158 if (mat.hasPerspective()) {
159 m -= 1; // try another non-persp matrix
160 continue;
161 }
162
163 // test a bunch of vectors. None should be scaled by more than stretch
164 // (modulo some error) and we should find a vector that is scaled by
165 // almost stretch.
166 static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
167 static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
168 SkScalar max = 0;
169 SkVector vectors[1000];
tomhudson@google.com83a44462011-10-27 15:27:51 +0000170 for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
bsalomon@google.com38396322011-09-09 19:32:04 +0000171 vectors[i].fX = rand.nextSScalar1();
172 vectors[i].fY = rand.nextSScalar1();
173 if (!vectors[i].normalize()) {
174 i -= 1;
175 continue;
176 }
177 }
178 mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
tomhudson@google.com83a44462011-10-27 15:27:51 +0000179 for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
bsalomon@google.com38396322011-09-09 19:32:04 +0000180 SkScalar d = vectors[i].length();
181 REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
182 if (max < d) {
183 max = d;
184 }
185 }
186 REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
187 }
188}
189
reed@android.comed673312009-02-27 16:24:51 +0000190void TestMatrix(skiatest::Reporter* reporter) {
191 SkMatrix mat, inverse, iden1, iden2;
192
193 mat.reset();
194 mat.setTranslate(SK_Scalar1, SK_Scalar1);
195 mat.invert(&inverse);
196 iden1.setConcat(mat, inverse);
197 REPORTER_ASSERT(reporter, is_identity(iden1));
198
199 mat.setScale(SkIntToScalar(2), SkIntToScalar(2));
200 mat.invert(&inverse);
201 iden1.setConcat(mat, inverse);
202 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000203 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000204
205 mat.setScale(SK_Scalar1/2, SK_Scalar1/2);
206 mat.invert(&inverse);
207 iden1.setConcat(mat, inverse);
208 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000209 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000210
211 mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
212 mat.postRotate(SkIntToScalar(25));
213 REPORTER_ASSERT(reporter, mat.invert(NULL));
214 mat.invert(&inverse);
215 iden1.setConcat(mat, inverse);
216 REPORTER_ASSERT(reporter, is_identity(iden1));
217 iden2.setConcat(inverse, mat);
218 REPORTER_ASSERT(reporter, is_identity(iden2));
reed@android.com4b7577b2009-06-29 16:14:41 +0000219 test_flatten(reporter, mat);
220 test_flatten(reporter, iden2);
reed@android.com80e39a72009-04-02 16:59:40 +0000221
reed@android.comed673312009-02-27 16:24:51 +0000222 // rectStaysRect test
223 {
224 static const struct {
225 SkScalar m00, m01, m10, m11;
226 bool mStaysRect;
227 }
228 gRectStaysRectSamples[] = {
229 { 0, 0, 0, 0, false },
230 { 0, 0, 0, SK_Scalar1, false },
231 { 0, 0, SK_Scalar1, 0, false },
232 { 0, 0, SK_Scalar1, SK_Scalar1, false },
233 { 0, SK_Scalar1, 0, 0, false },
234 { 0, SK_Scalar1, 0, SK_Scalar1, false },
235 { 0, SK_Scalar1, SK_Scalar1, 0, true },
236 { 0, SK_Scalar1, SK_Scalar1, SK_Scalar1, false },
237 { SK_Scalar1, 0, 0, 0, false },
238 { SK_Scalar1, 0, 0, SK_Scalar1, true },
239 { SK_Scalar1, 0, SK_Scalar1, 0, false },
240 { SK_Scalar1, 0, SK_Scalar1, SK_Scalar1, false },
241 { SK_Scalar1, SK_Scalar1, 0, 0, false },
242 { SK_Scalar1, SK_Scalar1, 0, SK_Scalar1, false },
243 { SK_Scalar1, SK_Scalar1, SK_Scalar1, 0, false },
244 { SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, false }
245 };
reed@android.com80e39a72009-04-02 16:59:40 +0000246
reed@android.comed673312009-02-27 16:24:51 +0000247 for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
248 SkMatrix m;
reed@android.com80e39a72009-04-02 16:59:40 +0000249
reed@android.comed673312009-02-27 16:24:51 +0000250 m.reset();
251 m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
252 m.set(SkMatrix::kMSkewX, gRectStaysRectSamples[i].m01);
253 m.set(SkMatrix::kMSkewY, gRectStaysRectSamples[i].m10);
254 m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
255 REPORTER_ASSERT(reporter,
256 m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
257 }
258 }
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000259
bungeman@google.comba7983e2011-07-13 20:18:16 +0000260 mat.reset();
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000261 mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
262 mat.set(SkMatrix::kMSkewX, SkIntToScalar(2));
263 mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
264 mat.set(SkMatrix::kMSkewY, SkIntToScalar(4));
265 mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
266 mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000267 SkScalar affine[6];
268 REPORTER_ASSERT(reporter, mat.asAffine(affine));
269
270 #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
271 REPORTER_ASSERT(reporter, affineEqual(ScaleX));
272 REPORTER_ASSERT(reporter, affineEqual(SkewY));
273 REPORTER_ASSERT(reporter, affineEqual(SkewX));
274 REPORTER_ASSERT(reporter, affineEqual(ScaleY));
275 REPORTER_ASSERT(reporter, affineEqual(TransX));
276 REPORTER_ASSERT(reporter, affineEqual(TransY));
277 #undef affineEqual
278
bungeman@google.com07faed12011-10-07 21:55:56 +0000279 mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000280 REPORTER_ASSERT(reporter, !mat.asAffine(affine));
bsalomon@google.com38396322011-09-09 19:32:04 +0000281
bsalomon@google.com8fe84b52012-03-26 15:24:27 +0000282 SkMatrix mat2;
283 mat2.reset();
284 mat.reset();
285 SkScalar zero = 0;
286 mat.set(SkMatrix::kMSkewX, -zero);
287 REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
288
289 mat2.reset();
290 mat.reset();
291 mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
292 mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
bsalomon@google.com9ed2ecd2012-03-26 15:57:37 +0000293 // fixed pt doesn't have the property that NaN does not equal itself.
294#ifdef SK_SCALAR_IS_FIXED
295 REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
296#else
bsalomon@google.com8fe84b52012-03-26 15:24:27 +0000297 REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
bsalomon@google.com9ed2ecd2012-03-26 15:57:37 +0000298#endif
bsalomon@google.com8fe84b52012-03-26 15:24:27 +0000299
bsalomon@google.com38396322011-09-09 19:32:04 +0000300 test_matrix_max_stretch(reporter);
reed@android.comed673312009-02-27 16:24:51 +0000301}
302
reed@android.comd8730ea2009-02-27 22:06:06 +0000303#include "TestClassDef.h"
304DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)