blob: 4638fe19757b1be8c8144d79b08a99e8a5f34d96 [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"
9#include "SkMatrix.h"
bsalomon@google.com38396322011-09-09 19:32:04 +000010#include "SkRandom.h"
reed@android.comed673312009-02-27 16:24:51 +000011
12static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
epoger@google.com2047f002011-05-17 17:36:59 +000013 // Note that we get more compounded error for multiple operations when
14 // SK_SCALAR_IS_FIXED.
reed@android.comed673312009-02-27 16:24:51 +000015#ifdef SK_SCALAR_IS_FLOAT
epoger@google.com2047f002011-05-17 17:36:59 +000016 const SkScalar tolerance = SK_Scalar1 / 200000;
reed@android.comed673312009-02-27 16:24:51 +000017#else
epoger@google.com2047f002011-05-17 17:36:59 +000018 const SkScalar tolerance = SK_Scalar1 / 1024;
reed@android.comed673312009-02-27 16:24:51 +000019#endif
20
21 return SkScalarAbs(a - b) <= tolerance;
22}
23
24static bool nearly_equal(const SkMatrix& a, const SkMatrix& b) {
25 for (int i = 0; i < 9; i++) {
26 if (!nearly_equal_scalar(a[i], b[i])) {
reed@android.comd4134452011-02-09 02:24:26 +000027 printf("not equal %g %g\n", (float)a[i], (float)b[i]);
reed@android.comed673312009-02-27 16:24:51 +000028 return false;
29 }
30 }
31 return true;
32}
33
34static bool is_identity(const SkMatrix& m) {
35 SkMatrix identity;
reed@android.com80e39a72009-04-02 16:59:40 +000036 identity.reset();
reed@android.comed673312009-02-27 16:24:51 +000037 return nearly_equal(m, identity);
38}
39
reed@android.com4b7577b2009-06-29 16:14:41 +000040static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
41 // add 100 in case we have a bug, I don't want to kill my stack in the test
42 char buffer[SkMatrix::kMaxFlattenSize + 100];
43 uint32_t size1 = m.flatten(NULL);
44 uint32_t size2 = m.flatten(buffer);
45 REPORTER_ASSERT(reporter, size1 == size2);
46 REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
47
48 SkMatrix m2;
49 uint32_t size3 = m2.unflatten(buffer);
50 REPORTER_ASSERT(reporter, size1 == size2);
51 REPORTER_ASSERT(reporter, m == m2);
52
53 char buffer2[SkMatrix::kMaxFlattenSize + 100];
54 size3 = m2.flatten(buffer2);
55 REPORTER_ASSERT(reporter, size1 == size2);
56 REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
57}
58
bsalomon@google.com38396322011-09-09 19:32:04 +000059void test_matrix_max_stretch(skiatest::Reporter* reporter) {
60 SkMatrix identity;
61 identity.reset();
62 REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
63
64 SkMatrix scale;
65 scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
66 REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
67
68 SkMatrix rot90Scale;
69 rot90Scale.setRotate(90 * SK_Scalar1);
70 rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
71 REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
72
73 SkMatrix rotate;
74 rotate.setRotate(128 * SK_Scalar1);
75 REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
76
77 SkMatrix translate;
78 translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
79 REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
80
81 SkMatrix perspX;
82 perspX.reset();
83 perspX.setPerspX(SK_Scalar1 / 1000);
84 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
85
86 SkMatrix perspY;
87 perspY.reset();
88 perspY.setPerspX(-SK_Scalar1 / 500);
89 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
90
91 SkMatrix baseMats[] = {scale, rot90Scale, rotate,
92 translate, perspX, perspY};
93 SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
94 for (int i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
95 mats[i] = baseMats[i];
96 bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
97 REPORTER_ASSERT(reporter, invertable);
98 }
99 SkRandom rand;
100 for (int m = 0; m < 1000; ++m) {
101 SkMatrix mat;
102 mat.reset();
103 for (int i = 0; i < 4; ++i) {
104 int x = rand.nextU() % SK_ARRAY_COUNT(mats);
105 mat.postConcat(mats[x]);
106 }
107 SkScalar stretch = mat.getMaxStretch();
108
109 if ((stretch < 0) != mat.hasPerspective()) {
110 stretch = mat.getMaxStretch();
111 }
112
113 REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
114
115 if (mat.hasPerspective()) {
116 m -= 1; // try another non-persp matrix
117 continue;
118 }
119
120 // test a bunch of vectors. None should be scaled by more than stretch
121 // (modulo some error) and we should find a vector that is scaled by
122 // almost stretch.
123 static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
124 static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
125 SkScalar max = 0;
126 SkVector vectors[1000];
127 for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
128 vectors[i].fX = rand.nextSScalar1();
129 vectors[i].fY = rand.nextSScalar1();
130 if (!vectors[i].normalize()) {
131 i -= 1;
132 continue;
133 }
134 }
135 mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
136 for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
137 SkScalar d = vectors[i].length();
138 REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
139 if (max < d) {
140 max = d;
141 }
142 }
143 REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
144 }
145}
146
reed@android.comed673312009-02-27 16:24:51 +0000147void TestMatrix(skiatest::Reporter* reporter) {
148 SkMatrix mat, inverse, iden1, iden2;
149
150 mat.reset();
151 mat.setTranslate(SK_Scalar1, SK_Scalar1);
152 mat.invert(&inverse);
153 iden1.setConcat(mat, inverse);
154 REPORTER_ASSERT(reporter, is_identity(iden1));
155
156 mat.setScale(SkIntToScalar(2), SkIntToScalar(2));
157 mat.invert(&inverse);
158 iden1.setConcat(mat, inverse);
159 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000160 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000161
162 mat.setScale(SK_Scalar1/2, SK_Scalar1/2);
163 mat.invert(&inverse);
164 iden1.setConcat(mat, inverse);
165 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000166 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000167
168 mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
169 mat.postRotate(SkIntToScalar(25));
170 REPORTER_ASSERT(reporter, mat.invert(NULL));
171 mat.invert(&inverse);
172 iden1.setConcat(mat, inverse);
173 REPORTER_ASSERT(reporter, is_identity(iden1));
174 iden2.setConcat(inverse, mat);
175 REPORTER_ASSERT(reporter, is_identity(iden2));
reed@android.com4b7577b2009-06-29 16:14:41 +0000176 test_flatten(reporter, mat);
177 test_flatten(reporter, iden2);
reed@android.com80e39a72009-04-02 16:59:40 +0000178
reed@android.comed673312009-02-27 16:24:51 +0000179 // rectStaysRect test
180 {
181 static const struct {
182 SkScalar m00, m01, m10, m11;
183 bool mStaysRect;
184 }
185 gRectStaysRectSamples[] = {
186 { 0, 0, 0, 0, false },
187 { 0, 0, 0, SK_Scalar1, false },
188 { 0, 0, SK_Scalar1, 0, false },
189 { 0, 0, SK_Scalar1, SK_Scalar1, false },
190 { 0, SK_Scalar1, 0, 0, false },
191 { 0, SK_Scalar1, 0, SK_Scalar1, false },
192 { 0, SK_Scalar1, SK_Scalar1, 0, true },
193 { 0, SK_Scalar1, SK_Scalar1, SK_Scalar1, false },
194 { SK_Scalar1, 0, 0, 0, false },
195 { SK_Scalar1, 0, 0, SK_Scalar1, true },
196 { SK_Scalar1, 0, SK_Scalar1, 0, false },
197 { SK_Scalar1, 0, SK_Scalar1, SK_Scalar1, false },
198 { SK_Scalar1, SK_Scalar1, 0, 0, false },
199 { SK_Scalar1, SK_Scalar1, 0, SK_Scalar1, false },
200 { SK_Scalar1, SK_Scalar1, SK_Scalar1, 0, false },
201 { SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, false }
202 };
reed@android.com80e39a72009-04-02 16:59:40 +0000203
reed@android.comed673312009-02-27 16:24:51 +0000204 for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
205 SkMatrix m;
reed@android.com80e39a72009-04-02 16:59:40 +0000206
reed@android.comed673312009-02-27 16:24:51 +0000207 m.reset();
208 m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
209 m.set(SkMatrix::kMSkewX, gRectStaysRectSamples[i].m01);
210 m.set(SkMatrix::kMSkewY, gRectStaysRectSamples[i].m10);
211 m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
212 REPORTER_ASSERT(reporter,
213 m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
214 }
215 }
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000216
bungeman@google.comba7983e2011-07-13 20:18:16 +0000217 mat.reset();
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000218 mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
219 mat.set(SkMatrix::kMSkewX, SkIntToScalar(2));
220 mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
221 mat.set(SkMatrix::kMSkewY, SkIntToScalar(4));
222 mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
223 mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000224 SkScalar affine[6];
225 REPORTER_ASSERT(reporter, mat.asAffine(affine));
226
227 #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
228 REPORTER_ASSERT(reporter, affineEqual(ScaleX));
229 REPORTER_ASSERT(reporter, affineEqual(SkewY));
230 REPORTER_ASSERT(reporter, affineEqual(SkewX));
231 REPORTER_ASSERT(reporter, affineEqual(ScaleY));
232 REPORTER_ASSERT(reporter, affineEqual(TransX));
233 REPORTER_ASSERT(reporter, affineEqual(TransY));
234 #undef affineEqual
235
236 mat.set(SkMatrix::kMPersp1, SkIntToScalar(1));
237 REPORTER_ASSERT(reporter, !mat.asAffine(affine));
bsalomon@google.com38396322011-09-09 19:32:04 +0000238
239 test_matrix_max_stretch(reporter);
reed@android.comed673312009-02-27 16:24:51 +0000240}
241
reed@android.comd8730ea2009-02-27 22:06:06 +0000242#include "TestClassDef.h"
243DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)