MatrixTest.cpp revision e40591d5484762067f976d979c506f126c7779e1
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 = *SkTCast<int*>(&aVal);
48                int bValI = *SkTCast<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 = *SkTCast<int*>(&aVal);
62                int bValI = *SkTCast<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_matrix_recttorect(skiatest::Reporter* reporter) {
85    SkRect src, dst;
86    SkMatrix matrix;
87
88    src.set(0, 0, SK_Scalar1*10, SK_Scalar1*10);
89    dst = src;
90    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
91    REPORTER_ASSERT(reporter, SkMatrix::kIdentity_Mask == matrix.getType());
92    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
93
94    dst.offset(SK_Scalar1, SK_Scalar1);
95    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
96    REPORTER_ASSERT(reporter, SkMatrix::kTranslate_Mask == matrix.getType());
97    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
98
99    dst.fRight += SK_Scalar1;
100    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
101    REPORTER_ASSERT(reporter,
102                    (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask) == matrix.getType());
103    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
104
105    dst = src;
106    dst.fRight = src.fRight * 2;
107    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
108    REPORTER_ASSERT(reporter, SkMatrix::kScale_Mask == matrix.getType());
109    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
110}
111
112static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
113    // add 100 in case we have a bug, I don't want to kill my stack in the test
114    char buffer[SkMatrix::kMaxFlattenSize + 100];
115    uint32_t size1 = m.writeToMemory(NULL);
116    uint32_t size2 = m.writeToMemory(buffer);
117    REPORTER_ASSERT(reporter, size1 == size2);
118    REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
119
120    SkMatrix m2;
121    uint32_t size3 = m2.readFromMemory(buffer);
122    REPORTER_ASSERT(reporter, size1 == size3);
123    REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
124
125    char buffer2[SkMatrix::kMaxFlattenSize + 100];
126    size3 = m2.writeToMemory(buffer2);
127    REPORTER_ASSERT(reporter, size1 == size3);
128    REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
129}
130
131static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
132    SkMatrix identity;
133    identity.reset();
134    REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
135
136    SkMatrix scale;
137    scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
138    REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
139
140    SkMatrix rot90Scale;
141    rot90Scale.setRotate(90 * SK_Scalar1);
142    rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
143    REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
144
145    SkMatrix rotate;
146    rotate.setRotate(128 * SK_Scalar1);
147    REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
148
149    SkMatrix translate;
150    translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
151    REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
152
153    SkMatrix perspX;
154    perspX.reset();
155    perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
156    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
157
158    SkMatrix perspY;
159    perspY.reset();
160    perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
161    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
162
163    SkMatrix baseMats[] = {scale, rot90Scale, rotate,
164                           translate, perspX, perspY};
165    SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
166    for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
167        mats[i] = baseMats[i];
168        bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
169        REPORTER_ASSERT(reporter, invertable);
170    }
171    SkRandom rand;
172    for (int m = 0; m < 1000; ++m) {
173        SkMatrix mat;
174        mat.reset();
175        for (int i = 0; i < 4; ++i) {
176            int x = rand.nextU() % SK_ARRAY_COUNT(mats);
177            mat.postConcat(mats[x]);
178        }
179        SkScalar stretch = mat.getMaxStretch();
180
181        if ((stretch < 0) != mat.hasPerspective()) {
182            stretch = mat.getMaxStretch();
183        }
184
185        REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
186
187        if (mat.hasPerspective()) {
188            m -= 1; // try another non-persp matrix
189            continue;
190        }
191
192        // test a bunch of vectors. None should be scaled by more than stretch
193        // (modulo some error) and we should find a vector that is scaled by
194        // almost stretch.
195        static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
196        static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
197        SkScalar max = 0;
198        SkVector vectors[1000];
199        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
200            vectors[i].fX = rand.nextSScalar1();
201            vectors[i].fY = rand.nextSScalar1();
202            if (!vectors[i].normalize()) {
203                i -= 1;
204                continue;
205            }
206        }
207        mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
208        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
209            SkScalar d = vectors[i].length();
210            REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
211            if (max < d) {
212                max = d;
213            }
214        }
215        REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
216    }
217}
218
219static void test_matrix_is_similarity(skiatest::Reporter* reporter) {
220    SkMatrix mat;
221
222    // identity
223    mat.setIdentity();
224    REPORTER_ASSERT(reporter, mat.isSimilarity());
225
226    // translation only
227    mat.reset();
228    mat.setTranslate(SkIntToScalar(100), SkIntToScalar(100));
229    REPORTER_ASSERT(reporter, mat.isSimilarity());
230
231    // scale with same size
232    mat.reset();
233    mat.setScale(SkIntToScalar(15), SkIntToScalar(15));
234    REPORTER_ASSERT(reporter, mat.isSimilarity());
235
236    // scale with one negative
237    mat.reset();
238    mat.setScale(SkIntToScalar(-15), SkIntToScalar(15));
239    REPORTER_ASSERT(reporter, mat.isSimilarity());
240
241    // scale with different size
242    mat.reset();
243    mat.setScale(SkIntToScalar(15), SkIntToScalar(20));
244    REPORTER_ASSERT(reporter, !mat.isSimilarity());
245
246    // scale with same size at a pivot point
247    mat.reset();
248    mat.setScale(SkIntToScalar(15), SkIntToScalar(15),
249                 SkIntToScalar(2), SkIntToScalar(2));
250    REPORTER_ASSERT(reporter, mat.isSimilarity());
251
252    // scale with different size at a pivot point
253    mat.reset();
254    mat.setScale(SkIntToScalar(15), SkIntToScalar(20),
255                 SkIntToScalar(2), SkIntToScalar(2));
256    REPORTER_ASSERT(reporter, !mat.isSimilarity());
257
258    // skew with same size
259    mat.reset();
260    mat.setSkew(SkIntToScalar(15), SkIntToScalar(15));
261    REPORTER_ASSERT(reporter, !mat.isSimilarity());
262
263    // skew with different size
264    mat.reset();
265    mat.setSkew(SkIntToScalar(15), SkIntToScalar(20));
266    REPORTER_ASSERT(reporter, !mat.isSimilarity());
267
268    // skew with same size at a pivot point
269    mat.reset();
270    mat.setSkew(SkIntToScalar(15), SkIntToScalar(15),
271                SkIntToScalar(2), SkIntToScalar(2));
272    REPORTER_ASSERT(reporter, !mat.isSimilarity());
273
274    // skew with different size at a pivot point
275    mat.reset();
276    mat.setSkew(SkIntToScalar(15), SkIntToScalar(20),
277                SkIntToScalar(2), SkIntToScalar(2));
278    REPORTER_ASSERT(reporter, !mat.isSimilarity());
279
280    // perspective x
281    mat.reset();
282    mat.setPerspX(SkScalarToPersp(SK_Scalar1 / 2));
283    REPORTER_ASSERT(reporter, !mat.isSimilarity());
284
285    // perspective y
286    mat.reset();
287    mat.setPerspY(SkScalarToPersp(SK_Scalar1 / 2));
288    REPORTER_ASSERT(reporter, !mat.isSimilarity());
289
290#if SK_SCALAR_IS_FLOAT
291    /* We bypass the following tests for SK_SCALAR_IS_FIXED build.
292     * The long discussion can be found in this issue:
293     *     http://codereview.appspot.com/5999050/
294     * In short, we haven't found a perfect way to fix the precision
295     * issue, i.e. the way we use tolerance in isSimilarityTransformation
296     * is incorrect. The situation becomes worse in fixed build, so
297     * we disabled rotation related tests for fixed build.
298     */
299
300    // rotate
301    for (int angle = 0; angle < 360; ++angle) {
302        mat.reset();
303        mat.setRotate(SkIntToScalar(angle));
304        REPORTER_ASSERT(reporter, mat.isSimilarity());
305    }
306
307    // see if there are any accumulated precision issues
308    mat.reset();
309    for (int i = 1; i < 360; i++) {
310        mat.postRotate(SkIntToScalar(1));
311    }
312    REPORTER_ASSERT(reporter, mat.isSimilarity());
313
314    // rotate + translate
315    mat.reset();
316    mat.setRotate(SkIntToScalar(30));
317    mat.postTranslate(SkIntToScalar(10), SkIntToScalar(20));
318    REPORTER_ASSERT(reporter, mat.isSimilarity());
319
320    // rotate + uniform scale
321    mat.reset();
322    mat.setRotate(SkIntToScalar(30));
323    mat.postScale(SkIntToScalar(2), SkIntToScalar(2));
324    REPORTER_ASSERT(reporter, mat.isSimilarity());
325
326    // rotate + non-uniform scale
327    mat.reset();
328    mat.setRotate(SkIntToScalar(30));
329    mat.postScale(SkIntToScalar(3), SkIntToScalar(2));
330    REPORTER_ASSERT(reporter, !mat.isSimilarity());
331#endif
332
333    // all zero
334    mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, 0);
335    REPORTER_ASSERT(reporter, !mat.isSimilarity());
336
337    // all zero except perspective
338    mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, SK_Scalar1);
339    REPORTER_ASSERT(reporter, !mat.isSimilarity());
340
341    // scales zero, only skews
342    mat.setAll(0, SK_Scalar1, 0,
343               SK_Scalar1, 0, 0,
344               0, 0, SkMatrix::I()[8]);
345    REPORTER_ASSERT(reporter, mat.isSimilarity());
346}
347
348static void TestMatrix(skiatest::Reporter* reporter) {
349    SkMatrix    mat, inverse, iden1, iden2;
350
351    mat.reset();
352    mat.setTranslate(SK_Scalar1, SK_Scalar1);
353    REPORTER_ASSERT(reporter, mat.invert(&inverse));
354    iden1.setConcat(mat, inverse);
355    REPORTER_ASSERT(reporter, is_identity(iden1));
356
357    mat.setScale(SkIntToScalar(2), SkIntToScalar(4));
358    REPORTER_ASSERT(reporter, mat.invert(&inverse));
359    iden1.setConcat(mat, inverse);
360    REPORTER_ASSERT(reporter, is_identity(iden1));
361    test_flatten(reporter, mat);
362
363    mat.setScale(SK_Scalar1/2, SkIntToScalar(2));
364    REPORTER_ASSERT(reporter, mat.invert(&inverse));
365    iden1.setConcat(mat, inverse);
366    REPORTER_ASSERT(reporter, is_identity(iden1));
367    test_flatten(reporter, mat);
368
369    mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
370    mat.postRotate(SkIntToScalar(25));
371    REPORTER_ASSERT(reporter, mat.invert(NULL));
372    REPORTER_ASSERT(reporter, mat.invert(&inverse));
373    iden1.setConcat(mat, inverse);
374    REPORTER_ASSERT(reporter, is_identity(iden1));
375    iden2.setConcat(inverse, mat);
376    REPORTER_ASSERT(reporter, is_identity(iden2));
377    test_flatten(reporter, mat);
378    test_flatten(reporter, iden2);
379
380    mat.setScale(0, SK_Scalar1);
381    REPORTER_ASSERT(reporter, !mat.invert(NULL));
382    REPORTER_ASSERT(reporter, !mat.invert(&inverse));
383    mat.setScale(SK_Scalar1, 0);
384    REPORTER_ASSERT(reporter, !mat.invert(NULL));
385    REPORTER_ASSERT(reporter, !mat.invert(&inverse));
386
387    // rectStaysRect test
388    {
389        static const struct {
390            SkScalar    m00, m01, m10, m11;
391            bool        mStaysRect;
392        }
393        gRectStaysRectSamples[] = {
394            {          0,          0,          0,           0, false },
395            {          0,          0,          0,  SK_Scalar1, false },
396            {          0,          0, SK_Scalar1,           0, false },
397            {          0,          0, SK_Scalar1,  SK_Scalar1, false },
398            {          0, SK_Scalar1,          0,           0, false },
399            {          0, SK_Scalar1,          0,  SK_Scalar1, false },
400            {          0, SK_Scalar1, SK_Scalar1,           0, true },
401            {          0, SK_Scalar1, SK_Scalar1,  SK_Scalar1, false },
402            { SK_Scalar1,          0,          0,           0, false },
403            { SK_Scalar1,          0,          0,  SK_Scalar1, true },
404            { SK_Scalar1,          0, SK_Scalar1,           0, false },
405            { SK_Scalar1,          0, SK_Scalar1,  SK_Scalar1, false },
406            { SK_Scalar1, SK_Scalar1,          0,           0, false },
407            { SK_Scalar1, SK_Scalar1,          0,  SK_Scalar1, false },
408            { SK_Scalar1, SK_Scalar1, SK_Scalar1,           0, false },
409            { SK_Scalar1, SK_Scalar1, SK_Scalar1,  SK_Scalar1, false }
410        };
411
412        for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
413            SkMatrix    m;
414
415            m.reset();
416            m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
417            m.set(SkMatrix::kMSkewX,  gRectStaysRectSamples[i].m01);
418            m.set(SkMatrix::kMSkewY,  gRectStaysRectSamples[i].m10);
419            m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
420            REPORTER_ASSERT(reporter,
421                    m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
422        }
423    }
424
425    mat.reset();
426    mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
427    mat.set(SkMatrix::kMSkewX,  SkIntToScalar(2));
428    mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
429    mat.set(SkMatrix::kMSkewY,  SkIntToScalar(4));
430    mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
431    mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
432    SkScalar affine[6];
433    REPORTER_ASSERT(reporter, mat.asAffine(affine));
434
435    #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
436    REPORTER_ASSERT(reporter, affineEqual(ScaleX));
437    REPORTER_ASSERT(reporter, affineEqual(SkewY));
438    REPORTER_ASSERT(reporter, affineEqual(SkewX));
439    REPORTER_ASSERT(reporter, affineEqual(ScaleY));
440    REPORTER_ASSERT(reporter, affineEqual(TransX));
441    REPORTER_ASSERT(reporter, affineEqual(TransY));
442    #undef affineEqual
443
444    mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
445    REPORTER_ASSERT(reporter, !mat.asAffine(affine));
446
447    SkMatrix mat2;
448    mat2.reset();
449    mat.reset();
450    SkScalar zero = 0;
451    mat.set(SkMatrix::kMSkewX, -zero);
452    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
453
454    mat2.reset();
455    mat.reset();
456    mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
457    mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
458    // fixed pt doesn't have the property that NaN does not equal itself.
459#ifdef SK_SCALAR_IS_FIXED
460    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
461#else
462    REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
463#endif
464
465    test_matrix_max_stretch(reporter);
466    test_matrix_is_similarity(reporter);
467    test_matrix_recttorect(reporter);
468}
469
470#include "TestClassDef.h"
471DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)
472