MatrixTest.cpp revision 42639cddc33746b351bbf07c540711eefffe191a
1
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 */
8#include "Test.h"
9#include "SkMath.h"
10#include "SkMatrix.h"
11#include "SkRandom.h"
12
13static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
14    // Note that we get more compounded error for multiple operations when
15    // SK_SCALAR_IS_FIXED.
16#ifdef SK_SCALAR_IS_FLOAT
17    const SkScalar tolerance = SK_Scalar1 / 200000;
18#else
19    const SkScalar tolerance = SK_Scalar1 / 1024;
20#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])) {
28            printf("not equal %g %g\n", (float)a[i], (float)b[i]);
29            return false;
30        }
31    }
32    return true;
33}
34
35static 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
42        if (equal) {
43            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
78static bool is_identity(const SkMatrix& m) {
79    SkMatrix identity;
80    identity.reset();
81    return nearly_equal(m, identity);
82}
83
84static 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);
95    REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
96
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
103static void 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();
127    perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
128    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
129
130    SkMatrix perspY;
131    perspY.reset();
132    perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
133    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)];
138    for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
139        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];
171        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
172            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));
180        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
181            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
191// This function is extracted from src/gpu/SkGpuDevice.cpp,
192// in order to make sure this function works correctly.
193static bool isSimilarityTransformation(const SkMatrix& matrix,
194                                       SkScalar tol = SK_ScalarNearlyZero) {
195    if (matrix.isIdentity() || matrix.getType() == SkMatrix::kTranslate_Mask) {
196        return true;
197    }
198    if (matrix.hasPerspective()) {
199        return false;
200    }
201
202    SkScalar mx = matrix.get(SkMatrix::kMScaleX);
203    SkScalar sx = matrix.get(SkMatrix::kMSkewX);
204    SkScalar my = matrix.get(SkMatrix::kMScaleY);
205    SkScalar sy = matrix.get(SkMatrix::kMSkewY);
206
207    if (mx == 0 && sx == 0 && my == 0 && sy == 0) {
208        return false;
209    }
210
211    // it has scales or skews, but it could also be rotation, check it out.
212    SkVector vec[2];
213    vec[0].set(mx, sx);
214    vec[1].set(sy, my);
215
216    return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)) &&
217           SkScalarNearlyEqual(vec[0].lengthSqd(), vec[1].lengthSqd(),
218                SkScalarSquare(tol));
219}
220
221static void test_matrix_is_similarity_transform(skiatest::Reporter* reporter) {
222    SkMatrix mat;
223
224    // identity
225    mat.setIdentity();
226    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
227
228    // translation only
229    mat.reset();
230    mat.setTranslate(SkIntToScalar(100), SkIntToScalar(100));
231    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
232
233    // scale with same size
234    mat.reset();
235    mat.setScale(SkIntToScalar(15), SkIntToScalar(15));
236    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
237
238    // scale with one negative
239    mat.reset();
240    mat.setScale(SkIntToScalar(-15), SkIntToScalar(15));
241    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
242
243    // scale with different size
244    mat.reset();
245    mat.setScale(SkIntToScalar(15), SkIntToScalar(20));
246    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
247
248    // scale with same size at a pivot point
249    mat.reset();
250    mat.setScale(SkIntToScalar(15), SkIntToScalar(15),
251                 SkIntToScalar(2), SkIntToScalar(2));
252    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
253
254    // scale with different size at a pivot point
255    mat.reset();
256    mat.setScale(SkIntToScalar(15), SkIntToScalar(20),
257                 SkIntToScalar(2), SkIntToScalar(2));
258    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
259
260    // skew with same size
261    mat.reset();
262    mat.setSkew(SkIntToScalar(15), SkIntToScalar(15));
263    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
264
265    // skew with different size
266    mat.reset();
267    mat.setSkew(SkIntToScalar(15), SkIntToScalar(20));
268    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
269
270    // skew with same size at a pivot point
271    mat.reset();
272    mat.setSkew(SkIntToScalar(15), SkIntToScalar(15),
273                SkIntToScalar(2), SkIntToScalar(2));
274    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
275
276    // skew with different size at a pivot point
277    mat.reset();
278    mat.setSkew(SkIntToScalar(15), SkIntToScalar(20),
279                SkIntToScalar(2), SkIntToScalar(2));
280    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
281
282    // perspective x
283    mat.reset();
284    mat.setPerspX(SkScalarToPersp(SK_Scalar1 / 2));
285    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
286
287    // perspective y
288    mat.reset();
289    mat.setPerspY(SkScalarToPersp(SK_Scalar1 / 2));
290    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
291
292#if SK_SCALAR_IS_FLOAT
293    /* We bypass the following tests for SK_SCALAR_IS_FIXED build.
294     * The long discussion can be found in this issue:
295     *     http://codereview.appspot.com/5999050/
296     * In short, we haven't found a perfect way to fix the precision
297     * issue, i.e. the way we use tolerance in isSimilarityTransformation
298     * is incorrect. The situation becomes worse in fixed build, so
299     * we disabled rotation related tests for fixed build.
300     */
301
302    // rotate
303    for (int angle = 0; angle < 360; ++angle) {
304        mat.reset();
305        mat.setRotate(SkIntToScalar(angle));
306        REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
307    }
308
309    // see if there are any accumulated precision issues
310    mat.reset();
311    for (int i = 1; i < 360; i++) {
312        mat.postRotate(SkIntToScalar(1));
313    }
314    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
315
316    // rotate + translate
317    mat.reset();
318    mat.setRotate(SkIntToScalar(30));
319    mat.postTranslate(SkIntToScalar(10), SkIntToScalar(20));
320    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
321
322    // rotate + uniform scale
323    mat.reset();
324    mat.setRotate(SkIntToScalar(30));
325    mat.postScale(SkIntToScalar(2), SkIntToScalar(2));
326    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
327
328    // rotate + non-uniform scale
329    mat.reset();
330    mat.setRotate(SkIntToScalar(30));
331    mat.postScale(SkIntToScalar(3), SkIntToScalar(2));
332    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
333#endif
334
335    // all zero
336    mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, 0);
337    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
338
339    // all zero except perspective
340    mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, SK_Scalar1);
341    REPORTER_ASSERT(reporter, !isSimilarityTransformation(mat));
342
343    // scales zero, only skews
344    mat.setAll(0, SK_Scalar1, 0,
345               SK_Scalar1, 0, 0,
346               0, 0, SkMatrix::I()[8]);
347    REPORTER_ASSERT(reporter, isSimilarityTransformation(mat));
348}
349
350static void TestMatrix(skiatest::Reporter* reporter) {
351    SkMatrix    mat, inverse, iden1, iden2;
352
353    mat.reset();
354    mat.setTranslate(SK_Scalar1, SK_Scalar1);
355    REPORTER_ASSERT(reporter, mat.invert(&inverse));
356    iden1.setConcat(mat, inverse);
357    REPORTER_ASSERT(reporter, is_identity(iden1));
358
359    mat.setScale(SkIntToScalar(2), SkIntToScalar(2));
360    REPORTER_ASSERT(reporter, mat.invert(&inverse));
361    iden1.setConcat(mat, inverse);
362    REPORTER_ASSERT(reporter, is_identity(iden1));
363    test_flatten(reporter, mat);
364
365    mat.setScale(SK_Scalar1/2, SK_Scalar1/2);
366    REPORTER_ASSERT(reporter, mat.invert(&inverse));
367    iden1.setConcat(mat, inverse);
368    REPORTER_ASSERT(reporter, is_identity(iden1));
369    test_flatten(reporter, mat);
370
371    mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
372    mat.postRotate(SkIntToScalar(25));
373    REPORTER_ASSERT(reporter, mat.invert(NULL));
374    REPORTER_ASSERT(reporter, mat.invert(&inverse));
375    iden1.setConcat(mat, inverse);
376    REPORTER_ASSERT(reporter, is_identity(iden1));
377    iden2.setConcat(inverse, mat);
378    REPORTER_ASSERT(reporter, is_identity(iden2));
379    test_flatten(reporter, mat);
380    test_flatten(reporter, iden2);
381
382    // rectStaysRect test
383    {
384        static const struct {
385            SkScalar    m00, m01, m10, m11;
386            bool        mStaysRect;
387        }
388        gRectStaysRectSamples[] = {
389            {          0,          0,          0,           0, false },
390            {          0,          0,          0,  SK_Scalar1, false },
391            {          0,          0, SK_Scalar1,           0, false },
392            {          0,          0, SK_Scalar1,  SK_Scalar1, false },
393            {          0, SK_Scalar1,          0,           0, false },
394            {          0, SK_Scalar1,          0,  SK_Scalar1, false },
395            {          0, SK_Scalar1, SK_Scalar1,           0, true },
396            {          0, SK_Scalar1, SK_Scalar1,  SK_Scalar1, false },
397            { SK_Scalar1,          0,          0,           0, false },
398            { SK_Scalar1,          0,          0,  SK_Scalar1, true },
399            { SK_Scalar1,          0, SK_Scalar1,           0, false },
400            { SK_Scalar1,          0, SK_Scalar1,  SK_Scalar1, false },
401            { SK_Scalar1, SK_Scalar1,          0,           0, false },
402            { SK_Scalar1, SK_Scalar1,          0,  SK_Scalar1, false },
403            { SK_Scalar1, SK_Scalar1, SK_Scalar1,           0, false },
404            { SK_Scalar1, SK_Scalar1, SK_Scalar1,  SK_Scalar1, false }
405        };
406
407        for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
408            SkMatrix    m;
409
410            m.reset();
411            m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
412            m.set(SkMatrix::kMSkewX,  gRectStaysRectSamples[i].m01);
413            m.set(SkMatrix::kMSkewY,  gRectStaysRectSamples[i].m10);
414            m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
415            REPORTER_ASSERT(reporter,
416                    m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
417        }
418    }
419
420    mat.reset();
421    mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
422    mat.set(SkMatrix::kMSkewX,  SkIntToScalar(2));
423    mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
424    mat.set(SkMatrix::kMSkewY,  SkIntToScalar(4));
425    mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
426    mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
427    SkScalar affine[6];
428    REPORTER_ASSERT(reporter, mat.asAffine(affine));
429
430    #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
431    REPORTER_ASSERT(reporter, affineEqual(ScaleX));
432    REPORTER_ASSERT(reporter, affineEqual(SkewY));
433    REPORTER_ASSERT(reporter, affineEqual(SkewX));
434    REPORTER_ASSERT(reporter, affineEqual(ScaleY));
435    REPORTER_ASSERT(reporter, affineEqual(TransX));
436    REPORTER_ASSERT(reporter, affineEqual(TransY));
437    #undef affineEqual
438
439    mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
440    REPORTER_ASSERT(reporter, !mat.asAffine(affine));
441
442    SkMatrix mat2;
443    mat2.reset();
444    mat.reset();
445    SkScalar zero = 0;
446    mat.set(SkMatrix::kMSkewX, -zero);
447    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
448
449    mat2.reset();
450    mat.reset();
451    mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
452    mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
453    // fixed pt doesn't have the property that NaN does not equal itself.
454#ifdef SK_SCALAR_IS_FIXED
455    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
456#else
457    REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
458#endif
459
460    test_matrix_max_stretch(reporter);
461    test_matrix_is_similarity_transform(reporter);
462}
463
464#include "TestClassDef.h"
465DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)
466