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