MatrixTest.cpp revision fab44db294846ff05d837b9cf0bf97a073891da7
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 "SkMatrixUtils.h"
12#include "SkRandom.h"
13
14static bool nearly_equal_scalar(SkScalar a, SkScalar b) {
15    // Note that we get more compounded error for multiple operations when
16    // SK_SCALAR_IS_FIXED.
17#ifdef SK_SCALAR_IS_FLOAT
18    const SkScalar tolerance = SK_Scalar1 / 200000;
19#else
20    const SkScalar tolerance = SK_Scalar1 / 1024;
21#endif
22
23    return SkScalarAbs(a - b) <= tolerance;
24}
25
26static bool nearly_equal(const SkMatrix& a, const SkMatrix& b) {
27    for (int i = 0; i < 9; i++) {
28        if (!nearly_equal_scalar(a[i], b[i])) {
29            SkDebugf("not equal %g %g\n", (float)a[i], (float)b[i]);
30            return false;
31        }
32    }
33    return true;
34}
35
36static bool are_equal(skiatest::Reporter* reporter,
37                      const SkMatrix& a,
38                      const SkMatrix& b) {
39    bool equal = a == b;
40    bool cheapEqual = a.cheapEqualTo(b);
41    if (equal != cheapEqual) {
42#ifdef SK_SCALAR_IS_FLOAT
43        if (equal) {
44            bool foundZeroSignDiff = false;
45            for (int i = 0; i < 9; ++i) {
46                float aVal = a.get(i);
47                float bVal = b.get(i);
48                int aValI = *SkTCast<int*>(&aVal);
49                int bValI = *SkTCast<int*>(&bVal);
50                if (0 == aVal && 0 == bVal && aValI != bValI) {
51                    foundZeroSignDiff = true;
52                } else {
53                    REPORTER_ASSERT(reporter, aVal == bVal && aValI == aValI);
54                }
55            }
56            REPORTER_ASSERT(reporter, foundZeroSignDiff);
57        } else {
58            bool foundNaN = false;
59            for (int i = 0; i < 9; ++i) {
60                float aVal = a.get(i);
61                float bVal = b.get(i);
62                int aValI = *SkTCast<int*>(&aVal);
63                int bValI = *SkTCast<int*>(&bVal);
64                if (sk_float_isnan(aVal) && aValI == bValI) {
65                    foundNaN = true;
66                } else {
67                    REPORTER_ASSERT(reporter, aVal == bVal && aValI == bValI);
68                }
69            }
70            REPORTER_ASSERT(reporter, foundNaN);
71        }
72#else
73        REPORTER_ASSERT(reporter, false);
74#endif
75    }
76    return equal;
77}
78
79static bool is_identity(const SkMatrix& m) {
80    SkMatrix identity;
81    identity.reset();
82    return nearly_equal(m, identity);
83}
84
85static void test_matrix_recttorect(skiatest::Reporter* reporter) {
86    SkRect src, dst;
87    SkMatrix matrix;
88
89    src.set(0, 0, SK_Scalar1*10, SK_Scalar1*10);
90    dst = src;
91    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
92    REPORTER_ASSERT(reporter, SkMatrix::kIdentity_Mask == matrix.getType());
93    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
94
95    dst.offset(SK_Scalar1, SK_Scalar1);
96    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
97    REPORTER_ASSERT(reporter, SkMatrix::kTranslate_Mask == matrix.getType());
98    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
99
100    dst.fRight += SK_Scalar1;
101    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
102    REPORTER_ASSERT(reporter,
103                    (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask) == matrix.getType());
104    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
105
106    dst = src;
107    dst.fRight = src.fRight * 2;
108    matrix.setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
109    REPORTER_ASSERT(reporter, SkMatrix::kScale_Mask == matrix.getType());
110    REPORTER_ASSERT(reporter, matrix.rectStaysRect());
111}
112
113static void test_flatten(skiatest::Reporter* reporter, const SkMatrix& m) {
114    // add 100 in case we have a bug, I don't want to kill my stack in the test
115    char buffer[SkMatrix::kMaxFlattenSize + 100];
116    uint32_t size1 = m.writeToMemory(NULL);
117    uint32_t size2 = m.writeToMemory(buffer);
118    REPORTER_ASSERT(reporter, size1 == size2);
119    REPORTER_ASSERT(reporter, size1 <= SkMatrix::kMaxFlattenSize);
120
121    SkMatrix m2;
122    uint32_t size3 = m2.readFromMemory(buffer);
123    REPORTER_ASSERT(reporter, size1 == size3);
124    REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
125
126    char buffer2[SkMatrix::kMaxFlattenSize + 100];
127    size3 = m2.writeToMemory(buffer2);
128    REPORTER_ASSERT(reporter, size1 == size3);
129    REPORTER_ASSERT(reporter, memcmp(buffer, buffer2, size1) == 0);
130}
131
132static void test_matrix_max_stretch(skiatest::Reporter* reporter) {
133    SkMatrix identity;
134    identity.reset();
135    REPORTER_ASSERT(reporter, SK_Scalar1 == identity.getMaxStretch());
136
137    SkMatrix scale;
138    scale.setScale(SK_Scalar1 * 2, SK_Scalar1 * 4);
139    REPORTER_ASSERT(reporter, SK_Scalar1 * 4 == scale.getMaxStretch());
140
141    SkMatrix rot90Scale;
142    rot90Scale.setRotate(90 * SK_Scalar1);
143    rot90Scale.postScale(SK_Scalar1 / 4, SK_Scalar1 / 2);
144    REPORTER_ASSERT(reporter, SK_Scalar1 / 2 == rot90Scale.getMaxStretch());
145
146    SkMatrix rotate;
147    rotate.setRotate(128 * SK_Scalar1);
148    REPORTER_ASSERT(reporter, SkScalarAbs(SK_Scalar1 - rotate.getMaxStretch()) <= SK_ScalarNearlyZero);
149
150    SkMatrix translate;
151    translate.setTranslate(10 * SK_Scalar1, -5 * SK_Scalar1);
152    REPORTER_ASSERT(reporter, SK_Scalar1 == translate.getMaxStretch());
153
154    SkMatrix perspX;
155    perspX.reset();
156    perspX.setPerspX(SkScalarToPersp(SK_Scalar1 / 1000));
157    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspX.getMaxStretch());
158
159    SkMatrix perspY;
160    perspY.reset();
161    perspY.setPerspX(SkScalarToPersp(-SK_Scalar1 / 500));
162    REPORTER_ASSERT(reporter, -SK_Scalar1 == perspY.getMaxStretch());
163
164    SkMatrix baseMats[] = {scale, rot90Scale, rotate,
165                           translate, perspX, perspY};
166    SkMatrix mats[2*SK_ARRAY_COUNT(baseMats)];
167    for (size_t i = 0; i < SK_ARRAY_COUNT(baseMats); ++i) {
168        mats[i] = baseMats[i];
169        bool invertable = mats[i].invert(&mats[i + SK_ARRAY_COUNT(baseMats)]);
170        REPORTER_ASSERT(reporter, invertable);
171    }
172    SkRandom rand;
173    for (int m = 0; m < 1000; ++m) {
174        SkMatrix mat;
175        mat.reset();
176        for (int i = 0; i < 4; ++i) {
177            int x = rand.nextU() % SK_ARRAY_COUNT(mats);
178            mat.postConcat(mats[x]);
179        }
180        SkScalar stretch = mat.getMaxStretch();
181
182        if ((stretch < 0) != mat.hasPerspective()) {
183            stretch = mat.getMaxStretch();
184        }
185
186        REPORTER_ASSERT(reporter, (stretch < 0) == mat.hasPerspective());
187
188        if (mat.hasPerspective()) {
189            m -= 1; // try another non-persp matrix
190            continue;
191        }
192
193        // test a bunch of vectors. None should be scaled by more than stretch
194        // (modulo some error) and we should find a vector that is scaled by
195        // almost stretch.
196        static const SkScalar gStretchTol = (105 * SK_Scalar1) / 100;
197        static const SkScalar gMaxStretchTol = (97 * SK_Scalar1) / 100;
198        SkScalar max = 0;
199        SkVector vectors[1000];
200        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
201            vectors[i].fX = rand.nextSScalar1();
202            vectors[i].fY = rand.nextSScalar1();
203            if (!vectors[i].normalize()) {
204                i -= 1;
205                continue;
206            }
207        }
208        mat.mapVectors(vectors, SK_ARRAY_COUNT(vectors));
209        for (size_t i = 0; i < SK_ARRAY_COUNT(vectors); ++i) {
210            SkScalar d = vectors[i].length();
211            REPORTER_ASSERT(reporter, SkScalarDiv(d, stretch) < gStretchTol);
212            if (max < d) {
213                max = d;
214            }
215        }
216        REPORTER_ASSERT(reporter, SkScalarDiv(max, stretch) >= gMaxStretchTol);
217    }
218}
219
220static void test_matrix_is_similarity(skiatest::Reporter* reporter) {
221    SkMatrix mat;
222
223    // identity
224    mat.setIdentity();
225    REPORTER_ASSERT(reporter, mat.isSimilarity());
226
227    // translation only
228    mat.reset();
229    mat.setTranslate(SkIntToScalar(100), SkIntToScalar(100));
230    REPORTER_ASSERT(reporter, mat.isSimilarity());
231
232    // scale with same size
233    mat.reset();
234    mat.setScale(SkIntToScalar(15), SkIntToScalar(15));
235    REPORTER_ASSERT(reporter, mat.isSimilarity());
236
237    // scale with one negative
238    mat.reset();
239    mat.setScale(SkIntToScalar(-15), SkIntToScalar(15));
240    REPORTER_ASSERT(reporter, mat.isSimilarity());
241
242    // scale with different size
243    mat.reset();
244    mat.setScale(SkIntToScalar(15), SkIntToScalar(20));
245    REPORTER_ASSERT(reporter, !mat.isSimilarity());
246
247    // scale with same size at a pivot point
248    mat.reset();
249    mat.setScale(SkIntToScalar(15), SkIntToScalar(15),
250                 SkIntToScalar(2), SkIntToScalar(2));
251    REPORTER_ASSERT(reporter, mat.isSimilarity());
252
253    // scale with different size at a pivot point
254    mat.reset();
255    mat.setScale(SkIntToScalar(15), SkIntToScalar(20),
256                 SkIntToScalar(2), SkIntToScalar(2));
257    REPORTER_ASSERT(reporter, !mat.isSimilarity());
258
259    // skew with same size
260    mat.reset();
261    mat.setSkew(SkIntToScalar(15), SkIntToScalar(15));
262    REPORTER_ASSERT(reporter, !mat.isSimilarity());
263
264    // skew with different size
265    mat.reset();
266    mat.setSkew(SkIntToScalar(15), SkIntToScalar(20));
267    REPORTER_ASSERT(reporter, !mat.isSimilarity());
268
269    // skew with same size at a pivot point
270    mat.reset();
271    mat.setSkew(SkIntToScalar(15), SkIntToScalar(15),
272                SkIntToScalar(2), SkIntToScalar(2));
273    REPORTER_ASSERT(reporter, !mat.isSimilarity());
274
275    // skew with different size at a pivot point
276    mat.reset();
277    mat.setSkew(SkIntToScalar(15), SkIntToScalar(20),
278                SkIntToScalar(2), SkIntToScalar(2));
279    REPORTER_ASSERT(reporter, !mat.isSimilarity());
280
281    // perspective x
282    mat.reset();
283    mat.setPerspX(SkScalarToPersp(SK_Scalar1 / 2));
284    REPORTER_ASSERT(reporter, !mat.isSimilarity());
285
286    // perspective y
287    mat.reset();
288    mat.setPerspY(SkScalarToPersp(SK_Scalar1 / 2));
289    REPORTER_ASSERT(reporter, !mat.isSimilarity());
290
291#ifdef SK_SCALAR_IS_FLOAT
292    /* We bypass the following tests for SK_SCALAR_IS_FIXED build.
293     * The long discussion can be found in this issue:
294     *     http://codereview.appspot.com/5999050/
295     * In short, we haven't found a perfect way to fix the precision
296     * issue, i.e. the way we use tolerance in isSimilarityTransformation
297     * is incorrect. The situation becomes worse in fixed build, so
298     * we disabled rotation related tests for fixed build.
299     */
300
301    // rotate
302    for (int angle = 0; angle < 360; ++angle) {
303        mat.reset();
304        mat.setRotate(SkIntToScalar(angle));
305        REPORTER_ASSERT(reporter, mat.isSimilarity());
306    }
307
308    // see if there are any accumulated precision issues
309    mat.reset();
310    for (int i = 1; i < 360; i++) {
311        mat.postRotate(SkIntToScalar(1));
312    }
313    REPORTER_ASSERT(reporter, mat.isSimilarity());
314
315    // rotate + translate
316    mat.reset();
317    mat.setRotate(SkIntToScalar(30));
318    mat.postTranslate(SkIntToScalar(10), SkIntToScalar(20));
319    REPORTER_ASSERT(reporter, mat.isSimilarity());
320
321    // rotate + uniform scale
322    mat.reset();
323    mat.setRotate(SkIntToScalar(30));
324    mat.postScale(SkIntToScalar(2), SkIntToScalar(2));
325    REPORTER_ASSERT(reporter, mat.isSimilarity());
326
327    // rotate + non-uniform scale
328    mat.reset();
329    mat.setRotate(SkIntToScalar(30));
330    mat.postScale(SkIntToScalar(3), SkIntToScalar(2));
331    REPORTER_ASSERT(reporter, !mat.isSimilarity());
332#endif
333
334    // all zero
335    mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, 0);
336    REPORTER_ASSERT(reporter, !mat.isSimilarity());
337
338    // all zero except perspective
339    mat.setAll(0, 0, 0, 0, 0, 0, 0, 0, SK_Scalar1);
340    REPORTER_ASSERT(reporter, !mat.isSimilarity());
341
342    // scales zero, only skews
343    mat.setAll(0, SK_Scalar1, 0,
344               SK_Scalar1, 0, 0,
345               0, 0, SkMatrix::I()[8]);
346    REPORTER_ASSERT(reporter, mat.isSimilarity());
347}
348
349// For test_matrix_decomposition, below.
350static bool scalar_nearly_equal_relative(SkScalar a, SkScalar b,
351                                         SkScalar tolerance = SK_ScalarNearlyZero) {
352    // from Bruce Dawson
353    // absolute check
354    SkScalar diff = SkScalarAbs(a - b);
355    if (diff < tolerance) {
356        return true;
357    }
358
359    // relative check
360    a = SkScalarAbs(a);
361    b = SkScalarAbs(b);
362    SkScalar largest = (b > a) ? b : a;
363
364    if (diff <= largest*tolerance) {
365        return true;
366    }
367
368    return false;
369}
370
371static bool check_matrix_recomposition(const SkMatrix& mat,
372                                       const SkPoint& rotation1,
373                                       const SkPoint& scale,
374                                       const SkPoint& rotation2) {
375    SkScalar c1 = rotation1.fX;
376    SkScalar s1 = rotation1.fY;
377    SkScalar scaleX = scale.fX;
378    SkScalar scaleY = scale.fY;
379    SkScalar c2 = rotation2.fX;
380    SkScalar s2 = rotation2.fY;
381
382    // We do a relative check here because large scale factors cause problems with an absolute check
383    bool result = scalar_nearly_equal_relative(mat[SkMatrix::kMScaleX],
384                                               scaleX*c1*c2 - scaleY*s1*s2) &&
385                  scalar_nearly_equal_relative(mat[SkMatrix::kMSkewX],
386                                               -scaleX*s1*c2 - scaleY*c1*s2) &&
387                  scalar_nearly_equal_relative(mat[SkMatrix::kMSkewY],
388                                               scaleX*c1*s2 + scaleY*s1*c2) &&
389                  scalar_nearly_equal_relative(mat[SkMatrix::kMScaleY],
390                                               -scaleX*s1*s2 + scaleY*c1*c2);
391    return result;
392}
393
394static void test_matrix_decomposition(skiatest::Reporter* reporter) {
395    SkMatrix mat;
396    SkPoint rotation1, scale, rotation2;
397
398    const float kRotation0 = 15.5f;
399    const float kRotation1 = -50.f;
400    const float kScale0 = 5000.f;
401    const float kScale1 = 0.001f;
402
403    // identity
404    mat.reset();
405    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
406    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
407    // make sure it doesn't crash if we pass in NULLs
408    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, NULL, NULL, NULL));
409
410    // rotation only
411    mat.setRotate(kRotation0);
412    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
413    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
414
415    // uniform scale only
416    mat.setScale(kScale0, kScale0);
417    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
418    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
419
420    // anisotropic scale only
421    mat.setScale(kScale1, kScale0);
422    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
423    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
424
425    // rotation then uniform scale
426    mat.setRotate(kRotation1);
427    mat.postScale(kScale0, kScale0);
428    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
429    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
430
431    // uniform scale then rotation
432    mat.setScale(kScale0, kScale0);
433    mat.postRotate(kRotation1);
434    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
435    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
436
437    // rotation then uniform scale+reflection
438    mat.setRotate(kRotation0);
439    mat.postScale(kScale1, -kScale1);
440    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
441    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
442
443    // uniform scale+reflection, then rotate
444    mat.setScale(kScale0, -kScale0);
445    mat.postRotate(kRotation1);
446    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
447    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
448
449    // rotation then anisotropic scale
450    mat.setRotate(kRotation1);
451    mat.postScale(kScale1, kScale0);
452    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
453    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
454
455    // rotation then anisotropic scale
456    mat.setRotate(90);
457    mat.postScale(kScale1, kScale0);
458    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
459    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
460
461    // anisotropic scale then rotation
462    mat.setScale(kScale1, kScale0);
463    mat.postRotate(kRotation0);
464    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
465    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
466
467    // anisotropic scale then rotation
468    mat.setScale(kScale1, kScale0);
469    mat.postRotate(90);
470    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
471    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
472
473    // rotation, uniform scale, then different rotation
474    mat.setRotate(kRotation1);
475    mat.postScale(kScale0, kScale0);
476    mat.postRotate(kRotation0);
477    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
478    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
479
480    // rotation, anisotropic scale, then different rotation
481    mat.setRotate(kRotation0);
482    mat.postScale(kScale1, kScale0);
483    mat.postRotate(kRotation1);
484    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
485    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
486
487    // rotation, anisotropic scale + reflection, then different rotation
488    mat.setRotate(kRotation0);
489    mat.postScale(-kScale1, kScale0);
490    mat.postRotate(kRotation1);
491    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
492    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
493
494    // try some random matrices
495    SkRandom rand;
496    for (int m = 0; m < 1000; ++m) {
497        SkScalar rot0 = rand.nextRangeF(-180, 180);
498        SkScalar sx = rand.nextRangeF(-3000.f, 3000.f);
499        SkScalar sy = rand.nextRangeF(-3000.f, 3000.f);
500        SkScalar rot1 = rand.nextRangeF(-180, 180);
501        mat.setRotate(rot0);
502        mat.postScale(sx, sy);
503        mat.postRotate(rot1);
504
505        if (SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2)) {
506            REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
507        } else {
508            // if the matrix is degenerate, the basis vectors should be near-parallel or near-zero
509            SkScalar perpdot = mat[SkMatrix::kMScaleX]*mat[SkMatrix::kMScaleY] -
510                               mat[SkMatrix::kMSkewX]*mat[SkMatrix::kMSkewY];
511            REPORTER_ASSERT(reporter, SkScalarNearlyZero(perpdot));
512        }
513    }
514
515    // translation shouldn't affect this
516    mat.postTranslate(-1000.f, 1000.f);
517    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
518    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
519
520    // perspective shouldn't affect this
521    mat[SkMatrix::kMPersp0] = 12.f;
522    mat[SkMatrix::kMPersp1] = 4.f;
523    mat[SkMatrix::kMPersp2] = 1872.f;
524    REPORTER_ASSERT(reporter, SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
525    REPORTER_ASSERT(reporter, check_matrix_recomposition(mat, rotation1, scale, rotation2));
526
527    // degenerate matrices
528    // mostly zero entries
529    mat.reset();
530    mat[SkMatrix::kMScaleX] = 0.f;
531    REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
532    mat.reset();
533    mat[SkMatrix::kMScaleY] = 0.f;
534    REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
535    mat.reset();
536    // linearly dependent entries
537    mat[SkMatrix::kMScaleX] = 1.f;
538    mat[SkMatrix::kMSkewX] = 2.f;
539    mat[SkMatrix::kMSkewY] = 4.f;
540    mat[SkMatrix::kMScaleY] = 8.f;
541    REPORTER_ASSERT(reporter, !SkDecomposeUpper2x2(mat, &rotation1, &scale, &rotation2));
542}
543
544// For test_matrix_homogeneous, below.
545static bool scalar_array_nearly_equal_relative(const SkScalar a[], const SkScalar b[], int count) {
546    for (int i = 0; i < count; ++i) {
547        if (!scalar_nearly_equal_relative(a[i], b[i])) {
548            return false;
549        }
550    }
551    return true;
552}
553
554// For test_matrix_homogeneous, below.
555// Maps a single triple in src using m and compares results to those in dst
556static bool naive_homogeneous_mapping(const SkMatrix& m, const SkScalar src[3],
557                                      const SkScalar dst[3]) {
558    SkScalar res[3];
559    SkScalar ms[9] = {m[0], m[1], m[2],
560                      m[3], m[4], m[5],
561                      m[6], m[7], m[8]};
562    res[0] = src[0] * ms[0] + src[1] * ms[1] + src[2] * ms[2];
563    res[1] = src[0] * ms[3] + src[1] * ms[4] + src[2] * ms[5];
564    res[2] = src[0] * ms[6] + src[1] * ms[7] + src[2] * ms[8];
565    return scalar_array_nearly_equal_relative(res, dst, 3);
566}
567
568static void test_matrix_homogeneous(skiatest::Reporter* reporter) {
569    SkMatrix mat;
570
571    const float kRotation0 = 15.5f;
572    const float kRotation1 = -50.f;
573    const float kScale0 = 5000.f;
574
575    const int kTripleCount = 1000;
576    const int kMatrixCount = 1000;
577    SkRandom rand;
578
579    SkScalar randTriples[3*kTripleCount];
580    for (int i = 0; i < 3*kTripleCount; ++i) {
581        randTriples[i] = rand.nextRangeF(-3000.f, 3000.f);
582    }
583
584    SkMatrix mats[kMatrixCount];
585    for (int i = 0; i < kMatrixCount; ++i) {
586        for (int j = 0; j < 9; ++j) {
587            mats[i].set(j, rand.nextRangeF(-3000.f, 3000.f));
588        }
589    }
590
591    // identity
592    {
593    mat.reset();
594    SkScalar dst[3*kTripleCount];
595    mat.mapHomogeneousPoints(dst, randTriples, kTripleCount);
596    REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(randTriples, dst, kTripleCount*3));
597    }
598
599    // zero matrix
600    {
601    mat.setAll(0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f);
602    SkScalar dst[3*kTripleCount];
603    mat.mapHomogeneousPoints(dst, randTriples, kTripleCount);
604    SkScalar zeros[3] = {0.f, 0.f, 0.f};
605    for (int i = 0; i < kTripleCount; ++i) {
606        REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(&dst[i*3], zeros, 3));
607    }
608    }
609
610    // zero point
611    {
612    SkScalar zeros[3] = {0.f, 0.f, 0.f};
613    for (int i = 0; i < kMatrixCount; ++i) {
614        SkScalar dst[3];
615        mats[i].mapHomogeneousPoints(dst, zeros, 1);
616        REPORTER_ASSERT(reporter, scalar_array_nearly_equal_relative(dst, zeros, 3));
617    }
618    }
619
620    // doesn't crash with null dst, src, count == 0
621    {
622    mats[0].mapHomogeneousPoints(NULL, NULL, 0);
623    }
624
625    // uniform scale of point
626    {
627    mat.setScale(kScale0, kScale0);
628    SkScalar dst[3];
629    SkScalar src[3] = {randTriples[0], randTriples[1], 1.f};
630    SkPoint pnt;
631    pnt.set(src[0], src[1]);
632    mat.mapHomogeneousPoints(dst, src, 1);
633    mat.mapPoints(&pnt, &pnt, 1);
634    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX));
635    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY));
636    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1));
637    }
638
639    // rotation of point
640    {
641    mat.setRotate(kRotation0);
642    SkScalar dst[3];
643    SkScalar src[3] = {randTriples[0], randTriples[1], 1.f};
644    SkPoint pnt;
645    pnt.set(src[0], src[1]);
646    mat.mapHomogeneousPoints(dst, src, 1);
647    mat.mapPoints(&pnt, &pnt, 1);
648    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX));
649    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY));
650    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1));
651    }
652
653    // rotation, scale, rotation of point
654    {
655    mat.setRotate(kRotation1);
656    mat.postScale(kScale0, kScale0);
657    mat.postRotate(kRotation0);
658    SkScalar dst[3];
659    SkScalar src[3] = {randTriples[0], randTriples[1], 1.f};
660    SkPoint pnt;
661    pnt.set(src[0], src[1]);
662    mat.mapHomogeneousPoints(dst, src, 1);
663    mat.mapPoints(&pnt, &pnt, 1);
664    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[0], pnt.fX));
665    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[1], pnt.fY));
666    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst[2], SK_Scalar1));
667    }
668
669    // compare with naive approach
670    {
671    for (int i = 0; i < kMatrixCount; ++i) {
672        for (int j = 0; j < kTripleCount; ++j) {
673            SkScalar dst[3];
674            mats[i].mapHomogeneousPoints(dst, &randTriples[j*3], 1);
675            REPORTER_ASSERT(reporter, naive_homogeneous_mapping(mats[i], &randTriples[j*3], dst));
676        }
677    }
678    }
679
680}
681
682static void TestMatrix(skiatest::Reporter* reporter) {
683    SkMatrix    mat, inverse, iden1, iden2;
684
685    mat.reset();
686    mat.setTranslate(SK_Scalar1, SK_Scalar1);
687    REPORTER_ASSERT(reporter, mat.invert(&inverse));
688    iden1.setConcat(mat, inverse);
689    REPORTER_ASSERT(reporter, is_identity(iden1));
690
691    mat.setScale(SkIntToScalar(2), SkIntToScalar(4));
692    REPORTER_ASSERT(reporter, mat.invert(&inverse));
693    iden1.setConcat(mat, inverse);
694    REPORTER_ASSERT(reporter, is_identity(iden1));
695    test_flatten(reporter, mat);
696
697    mat.setScale(SK_Scalar1/2, SkIntToScalar(2));
698    REPORTER_ASSERT(reporter, mat.invert(&inverse));
699    iden1.setConcat(mat, inverse);
700    REPORTER_ASSERT(reporter, is_identity(iden1));
701    test_flatten(reporter, mat);
702
703    mat.setScale(SkIntToScalar(3), SkIntToScalar(5), SkIntToScalar(20), 0);
704    mat.postRotate(SkIntToScalar(25));
705    REPORTER_ASSERT(reporter, mat.invert(NULL));
706    REPORTER_ASSERT(reporter, mat.invert(&inverse));
707    iden1.setConcat(mat, inverse);
708    REPORTER_ASSERT(reporter, is_identity(iden1));
709    iden2.setConcat(inverse, mat);
710    REPORTER_ASSERT(reporter, is_identity(iden2));
711    test_flatten(reporter, mat);
712    test_flatten(reporter, iden2);
713
714    mat.setScale(0, SK_Scalar1);
715    REPORTER_ASSERT(reporter, !mat.invert(NULL));
716    REPORTER_ASSERT(reporter, !mat.invert(&inverse));
717    mat.setScale(SK_Scalar1, 0);
718    REPORTER_ASSERT(reporter, !mat.invert(NULL));
719    REPORTER_ASSERT(reporter, !mat.invert(&inverse));
720
721    // rectStaysRect test
722    {
723        static const struct {
724            SkScalar    m00, m01, m10, m11;
725            bool        mStaysRect;
726        }
727        gRectStaysRectSamples[] = {
728            {          0,          0,          0,           0, false },
729            {          0,          0,          0,  SK_Scalar1, false },
730            {          0,          0, SK_Scalar1,           0, false },
731            {          0,          0, SK_Scalar1,  SK_Scalar1, false },
732            {          0, SK_Scalar1,          0,           0, false },
733            {          0, SK_Scalar1,          0,  SK_Scalar1, false },
734            {          0, SK_Scalar1, SK_Scalar1,           0, true },
735            {          0, SK_Scalar1, SK_Scalar1,  SK_Scalar1, false },
736            { SK_Scalar1,          0,          0,           0, false },
737            { SK_Scalar1,          0,          0,  SK_Scalar1, true },
738            { SK_Scalar1,          0, SK_Scalar1,           0, false },
739            { SK_Scalar1,          0, SK_Scalar1,  SK_Scalar1, false },
740            { SK_Scalar1, SK_Scalar1,          0,           0, false },
741            { SK_Scalar1, SK_Scalar1,          0,  SK_Scalar1, false },
742            { SK_Scalar1, SK_Scalar1, SK_Scalar1,           0, false },
743            { SK_Scalar1, SK_Scalar1, SK_Scalar1,  SK_Scalar1, false }
744        };
745
746        for (size_t i = 0; i < SK_ARRAY_COUNT(gRectStaysRectSamples); i++) {
747            SkMatrix    m;
748
749            m.reset();
750            m.set(SkMatrix::kMScaleX, gRectStaysRectSamples[i].m00);
751            m.set(SkMatrix::kMSkewX,  gRectStaysRectSamples[i].m01);
752            m.set(SkMatrix::kMSkewY,  gRectStaysRectSamples[i].m10);
753            m.set(SkMatrix::kMScaleY, gRectStaysRectSamples[i].m11);
754            REPORTER_ASSERT(reporter,
755                    m.rectStaysRect() == gRectStaysRectSamples[i].mStaysRect);
756        }
757    }
758
759    mat.reset();
760    mat.set(SkMatrix::kMScaleX, SkIntToScalar(1));
761    mat.set(SkMatrix::kMSkewX,  SkIntToScalar(2));
762    mat.set(SkMatrix::kMTransX, SkIntToScalar(3));
763    mat.set(SkMatrix::kMSkewY,  SkIntToScalar(4));
764    mat.set(SkMatrix::kMScaleY, SkIntToScalar(5));
765    mat.set(SkMatrix::kMTransY, SkIntToScalar(6));
766    SkScalar affine[6];
767    REPORTER_ASSERT(reporter, mat.asAffine(affine));
768
769    #define affineEqual(e) affine[SkMatrix::kA##e] == mat.get(SkMatrix::kM##e)
770    REPORTER_ASSERT(reporter, affineEqual(ScaleX));
771    REPORTER_ASSERT(reporter, affineEqual(SkewY));
772    REPORTER_ASSERT(reporter, affineEqual(SkewX));
773    REPORTER_ASSERT(reporter, affineEqual(ScaleY));
774    REPORTER_ASSERT(reporter, affineEqual(TransX));
775    REPORTER_ASSERT(reporter, affineEqual(TransY));
776    #undef affineEqual
777
778    mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
779    REPORTER_ASSERT(reporter, !mat.asAffine(affine));
780
781    SkMatrix mat2;
782    mat2.reset();
783    mat.reset();
784    SkScalar zero = 0;
785    mat.set(SkMatrix::kMSkewX, -zero);
786    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
787
788    mat2.reset();
789    mat.reset();
790    mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
791    mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
792    // fixed pt doesn't have the property that NaN does not equal itself.
793#ifdef SK_SCALAR_IS_FIXED
794    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
795#else
796    REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
797#endif
798
799    test_matrix_max_stretch(reporter);
800    test_matrix_is_similarity(reporter);
801    test_matrix_recttorect(reporter);
802    test_matrix_decomposition(reporter);
803    test_matrix_homogeneous(reporter);
804}
805
806#include "TestClassDef.h"
807DEFINE_TESTCLASS("Matrix", MatrixTestClass, TestMatrix)
808