blob: abc0d54fce9f90632661b123f2ccdc1d43418add [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
35static bool is_identity(const SkMatrix& m) {
36 SkMatrix identity;
reed@android.com80e39a72009-04-02 16:59:40 +000037 identity.reset();
reed@android.comed673312009-02-27 16:24:51 +000038 return nearly_equal(m, identity);
39}
40
reed@android.com4b7577b2009-06-29 16:14:41 +000041static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
42 // add 100 in case we have a bug, I don't want to kill my stack in the test
43 char buffer[SkMatrix::kMaxFlattenSize + 100];
44 uint32_t size1 = m.flatten(NULL);
45 uint32_t size2 = m.flatten(buffer);
46 REPORTER_ASSERT(reporter, size1 == size2);
47 REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
48
49 SkMatrix m2;
50 uint32_t size3 = m2.unflatten(buffer);
51 REPORTER_ASSERT(reporter, size1 == size2);
52 REPORTER_ASSERT(reporter, m == m2);
53
54 char buffer2[SkMatrix::kMaxFlattenSize + 100];
55 size3 = m2.flatten(buffer2);
56 REPORTER_ASSERT(reporter, size1 == size2);
57 REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
58}
59
bsalomon@google.com38396322011-09-09 19:32:04 +000060void test_matrix_max_stretch(skiatest::Reporter* reporter) {
61 SkMatrix identity;
62 identity.reset();
63 REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
64
65 SkMatrix scale;
66 scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
67 REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
68
69 SkMatrix rot90Scale;
70 rot90Scale.setRotate(90 * SK_Scalar1);
71 rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
72 REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
73
74 SkMatrix rotate;
75 rotate.setRotate(128 * SK_Scalar1);
76 REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
77
78 SkMatrix translate;
79 translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
80 REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
81
82 SkMatrix perspX;
83 perspX.reset();
84 perspX.setPerspX(SK_Scalar1 / 1000);
85 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
86
87 SkMatrix perspY;
88 perspY.reset();
89 perspY.setPerspX(-SK_Scalar1 / 500);
90 REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
91
92 SkMatrix baseMats[] = {scale, rot90Scale, rotate,
93 translate, perspX, perspY};
94 SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
95 for (int i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
96 mats[i] = baseMats[i];
97 bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
98 REPORTER_ASSERT(reporter, invertable);
99 }
100 SkRandom rand;
101 for (int m = 0; m < 1000; ++m) {
102 SkMatrix mat;
103 mat.reset();
104 for (int i = 0; i < 4; ++i) {
105 int x = rand.nextU() % SK_ARRAY_COUNT(mats);
106 mat.postConcat(mats[x]);
107 }
108 SkScalar stretch = mat.getMaxStretch();
109
110 if ((stretch < 0) != mat.hasPerspective()) {
111 stretch = mat.getMaxStretch();
112 }
113
114 REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
115
116 if (mat.hasPerspective()) {
117 m -= 1; // try another non-persp matrix
118 continue;
119 }
120
121 // test a bunch of vectors. None should be scaled by more than stretch
122 // (modulo some error) and we should find a vector that is scaled by
123 // almost stretch.
124 static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
125 static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
126 SkScalar max = 0;
127 SkVector vectors[1000];
128 for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
129 vectors[i].fX = rand.nextSScalar1();
130 vectors[i].fY = rand.nextSScalar1();
131 if (!vectors[i].normalize()) {
132 i -= 1;
133 continue;
134 }
135 }
136 mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
137 for (int i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
138 SkScalar d = vectors[i].length();
139 REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
140 if (max < d) {
141 max = d;
142 }
143 }
144 REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
145 }
146}
147
reed@android.comed673312009-02-27 16:24:51 +0000148void TestMatrix(skiatest::Reporter* reporter) {
149 SkMatrix mat, inverse, iden1, iden2;
150
151 mat.reset();
152 mat.setTranslate(SK_Scalar1, SK_Scalar1);
153 mat.invert(&inverse);
154 iden1.setConcat(mat, inverse);
155 REPORTER_ASSERT(reporter, is_identity(iden1));
156
157 mat.setScale(SkIntToScalar(2), SkIntToScalar(2));
158 mat.invert(&inverse);
159 iden1.setConcat(mat, inverse);
160 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000161 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000162
163 mat.setScale(SK_Scalar1/2, SK_Scalar1/2);
164 mat.invert(&inverse);
165 iden1.setConcat(mat, inverse);
166 REPORTER_ASSERT(reporter, is_identity(iden1));
reed@android.com4b7577b2009-06-29 16:14:41 +0000167 test_flatten(reporter, mat);
reed@android.comed673312009-02-27 16:24:51 +0000168
169 mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
170 mat.postRotate(SkIntToScalar(25));
171 REPORTER_ASSERT(reporter, mat.invert(NULL));
172 mat.invert(&inverse);
173 iden1.setConcat(mat, inverse);
174 REPORTER_ASSERT(reporter, is_identity(iden1));
175 iden2.setConcat(inverse, mat);
176 REPORTER_ASSERT(reporter, is_identity(iden2));
reed@android.com4b7577b2009-06-29 16:14:41 +0000177 test_flatten(reporter, mat);
178 test_flatten(reporter, iden2);
reed@android.com80e39a72009-04-02 16:59:40 +0000179
reed@android.comed673312009-02-27 16:24:51 +0000180 // rectStaysRect test
181 {
182 static const struct {
183 SkScalar m00, m01, m10, m11;
184 bool mStaysRect;
185 }
186 gRectStaysRectSamples[] = {
187 { 0, 0, 0, 0, false },
188 { 0, 0, 0, SK_Scalar1, false },
189 { 0, 0, SK_Scalar1, 0, false },
190 { 0, 0, SK_Scalar1, SK_Scalar1, false },
191 { 0, SK_Scalar1, 0, 0, false },
192 { 0, SK_Scalar1, 0, SK_Scalar1, false },
193 { 0, SK_Scalar1, SK_Scalar1, 0, true },
194 { 0, SK_Scalar1, SK_Scalar1, SK_Scalar1, false },
195 { SK_Scalar1, 0, 0, 0, false },
196 { SK_Scalar1, 0, 0, SK_Scalar1, true },
197 { SK_Scalar1, 0, SK_Scalar1, 0, false },
198 { SK_Scalar1, 0, SK_Scalar1, SK_Scalar1, false },
199 { SK_Scalar1, SK_Scalar1, 0, 0, false },
200 { SK_Scalar1, SK_Scalar1, 0, SK_Scalar1, false },
201 { SK_Scalar1, SK_Scalar1, SK_Scalar1, 0, false },
202 { SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, false }
203 };
reed@android.com80e39a72009-04-02 16:59:40 +0000204
reed@android.comed673312009-02-27 16:24:51 +0000205 for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
206 SkMatrix m;
reed@android.com80e39a72009-04-02 16:59:40 +0000207
reed@android.comed673312009-02-27 16:24:51 +0000208 m.reset();
209 m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
210 m.set(SkMatrix::kMSkewX, gRectStaysRectSamples[i].m01);
211 m.set(SkMatrix::kMSkewY, gRectStaysRectSamples[i].m10);
212 m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
213 REPORTER_ASSERT(reporter,
214 m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
215 }
216 }
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000217
bungeman@google.comba7983e2011-07-13 20:18:16 +0000218 mat.reset();
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000219 mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
220 mat.set(SkMatrix::kMSkewX, SkIntToScalar(2));
221 mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
222 mat.set(SkMatrix::kMSkewY, SkIntToScalar(4));
223 mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
224 mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
bungeman@google.com1ddd7c32011-07-13 19:41:55 +0000225 SkScalar affine[6];
226 REPORTER_ASSERT(reporter, mat.asAffine(affine));
227
228 #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
229 REPORTER_ASSERT(reporter, affineEqual(ScaleX));
230 REPORTER_ASSERT(reporter, affineEqual(SkewY));
231 REPORTER_ASSERT(reporter, affineEqual(SkewX));
232 REPORTER_ASSERT(reporter, affineEqual(ScaleY));
233 REPORTER_ASSERT(reporter, affineEqual(TransX));
234 REPORTER_ASSERT(reporter, affineEqual(TransY));
235 #undef affineEqual
236
237 mat.set(SkMatrix::kMPersp1, SkIntToScalar(1));
238 REPORTER_ASSERT(reporter, !mat.asAffine(affine));
bsalomon@google.com38396322011-09-09 19:32:04 +0000239
240 test_matrix_max_stretch(reporter);
reed@android.comed673312009-02-27 16:24:51 +0000241}
242
reed@android.comd8730ea2009-02-27 22:06:06 +0000243#include "TestClassDef.h"
244DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)