1/*
2 * Copyright 2015 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 "SkMatrix.h"
9#include "SkPath.h"
10#include "SkPathRef.h"
11#include "SkRRect.h"
12#include "Test.h"
13
14static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path,
15                                   SkPath::Direction* dir, unsigned* start) {
16    SkRRect out;
17    REPORTER_ASSERT(reporter, path.isRRect(&out, dir, start));
18    SkPath recreatedPath;
19    recreatedPath.addRRect(out, *dir, *start);
20    REPORTER_ASSERT(reporter, path == recreatedPath);
21    // Test that rotations/mirrors of the rrect path are still rrect paths and the returned
22    // parameters for the transformed paths are correct.
23    static const SkMatrix kMatrices[] = {
24        SkMatrix::MakeScale(1, 1),
25        SkMatrix::MakeScale(-1, 1),
26        SkMatrix::MakeScale(1, -1),
27        SkMatrix::MakeScale(-1, -1),
28    };
29    for (auto& m : kMatrices) {
30        SkPath xformed;
31        path.transform(m, &xformed);
32        SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty());
33        SkPath::Direction xd = SkPath::kCCW_Direction;
34        unsigned xs = ~0U;
35        REPORTER_ASSERT(reporter, xformed.isRRect(&xrr, &xd, &xs));
36        recreatedPath.reset();
37        recreatedPath.addRRect(xrr, xd, xs);
38        REPORTER_ASSERT(reporter, recreatedPath == xformed);
39    }
40    return out;
41}
42
43static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in,
44                                         SkPath::Direction dir, unsigned start) {
45    switch (in.getType()) {
46        case SkRRect::kEmpty_Type:
47        case SkRRect::kRect_Type:
48        case SkRRect::kOval_Type:
49            return in;
50        default:
51            break;
52    }
53    SkPath path;
54    path.addRRect(in, dir, start);
55    SkPath::Direction outDir;
56    unsigned outStart;
57    SkRRect rrect = path_contains_rrect(reporter, path, &outDir, &outStart);
58    REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
59    return rrect;
60}
61
62static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in,
63                                      SkPath::Direction dir, unsigned start) {
64    SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
65    if (in != out) {
66        SkDebugf("");
67    }
68    REPORTER_ASSERT(reporter, in == out);
69}
70
71static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in,
72                                        SkPath::Direction dir, unsigned start) {
73    SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
74    if (in == out) {
75        SkDebugf("");
76    }
77}
78
79static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r,
80        SkVector v[4], SkPath::Direction dir, unsigned start) {
81    SkRRect rrect;
82    rrect.setRectRadii(r, v);
83    path_contains_rrect_check(reporter, rrect, dir, start);
84}
85
86class ForceIsRRect_Private {
87public:
88    ForceIsRRect_Private(SkPath* path, SkPath::Direction dir, unsigned start) {
89        path->fPathRef->setIsRRect(true, dir == SkPath::kCCW_Direction, start);
90    }
91};
92
93static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path,
94                                      SkPath::Direction dir, unsigned start) {
95    ForceIsRRect_Private force_rrect(&path, dir, start);
96    SkPath::Direction outDir;
97    unsigned outStart;
98    path_contains_rrect(reporter, path, &outDir, &outStart);
99    REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
100}
101
102static void test_undetected_paths(skiatest::Reporter* reporter) {
103    // We use a dummy path to get the exact conic weight used by SkPath for a circular arc. This
104    // allows our local, hand-crafted, artisanal round rect paths below to exactly match the
105    // factory made corporate paths produced by SkPath.
106    SkPath dummyPath;
107    dummyPath.addCircle(0, 0, 10);
108    SkPath::RawIter iter(dummyPath);
109    SkPoint dummyPts[4];
110    SkPath::Verb v = iter.next(dummyPts);
111    REPORTER_ASSERT(reporter, SkPath::kMove_Verb == v);
112    v = iter.next(dummyPts);
113    REPORTER_ASSERT(reporter, SkPath::kConic_Verb == v);
114    const SkScalar weight = iter.conicWeight();
115
116    SkPath path;
117    path.moveTo(0, 62.5f);
118    path.lineTo(0, 3.5f);
119    path.conicTo(0, 0, 3.5f, 0, weight);
120    path.lineTo(196.5f, 0);
121    path.conicTo(200, 0, 200, 3.5f, weight);
122    path.lineTo(200, 62.5f);
123    path.conicTo(200, 66, 196.5f, 66, weight);
124    path.lineTo(3.5f, 66);
125    path.conicTo(0, 66, 0, 62.5, weight);
126    path.close();
127    force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
128
129    path.reset();
130    path.moveTo(0, 81.5f);
131    path.lineTo(0, 3.5f);
132    path.conicTo(0, 0, 3.5f, 0, weight);
133    path.lineTo(149.5, 0);
134    path.conicTo(153, 0, 153, 3.5f, weight);
135    path.lineTo(153, 81.5f);
136    path.conicTo(153, 85, 149.5f, 85, weight);
137    path.lineTo(3.5f, 85);
138    path.conicTo(0, 85, 0, 81.5f, weight);
139    path.close();
140    force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
141
142    path.reset();
143    path.moveTo(14, 1189);
144    path.lineTo(14, 21);
145    path.conicTo(14, 14, 21, 14, weight);
146    path.lineTo(1363, 14);
147    path.conicTo(1370, 14, 1370, 21, weight);
148    path.lineTo(1370, 1189);
149    path.conicTo(1370, 1196, 1363, 1196, weight);
150    path.lineTo(21, 1196);
151    path.conicTo(14, 1196, 14, 1189, weight);
152    path.close();
153    force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
154
155    path.reset();
156    path.moveTo(14, 1743);
157    path.lineTo(14, 21);
158    path.conicTo(14, 14, 21, 14, weight);
159    path.lineTo(1363, 14);
160    path.conicTo(1370, 14, 1370, 21, weight);
161    path.lineTo(1370, 1743);
162    path.conicTo(1370, 1750, 1363, 1750, weight);
163    path.lineTo(21, 1750);
164    path.conicTo(14, 1750, 14, 1743, weight);
165    path.close();
166    force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6);
167}
168
169static const SkScalar kWidth = 100.0f;
170static const SkScalar kHeight = 100.0f;
171
172static void test_tricky_radii(skiatest::Reporter* reporter) {
173    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
174        for (int start = 0; start < 8; ++start) {
175            {
176                // crbug.com/458522
177                SkRRect rr;
178                const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
179                const SkScalar rad = 12814;
180                const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
181                rr.setRectRadii(bounds, vec);
182                path_contains_rrect_check(reporter, rr, dir, start);
183            }
184
185            {
186                // crbug.com//463920
187                SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
188                SkVector radii[4] = {
189                    { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
190                };
191                SkRRect rr;
192                rr.setRectRadii(r, radii);
193                path_contains_rrect_nocheck(reporter, rr, dir, start);
194            }
195        }
196    }
197}
198
199static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
200    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
201        for (int start = 0; start < 8; ++start) {
202            SkRRect rr;
203            const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
204            const SkScalar rad = 40;
205            rr.setRectXY(bounds, rad, rad);
206            path_contains_rrect_check(reporter, rr, dir, start);
207
208            SkRRect other;
209            SkMatrix matrix;
210            matrix.setScale(0, 1);
211            rr.transform(matrix, &other);
212            path_contains_rrect_check(reporter, rr, dir, start);
213        }
214    }
215}
216
217static void test_inset(skiatest::Reporter* reporter) {
218    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
219        for (int start = 0; start < 8; ++start) {
220            SkRRect rr, rr2;
221            SkRect r = { 0, 0, 100, 100 };
222
223            rr.setRect(r);
224            rr.inset(-20, -20, &rr2);
225            path_contains_rrect_check(reporter, rr, dir, start);
226
227            rr.inset(20, 20, &rr2);
228            path_contains_rrect_check(reporter, rr, dir, start);
229
230            rr.inset(r.width()/2, r.height()/2, &rr2);
231            path_contains_rrect_check(reporter, rr, dir, start);
232
233            rr.setRectXY(r, 20, 20);
234            rr.inset(19, 19, &rr2);
235            path_contains_rrect_check(reporter, rr, dir, start);
236            rr.inset(20, 20, &rr2);
237            path_contains_rrect_check(reporter, rr, dir, start);
238        }
239    }
240}
241
242
243static void test_9patch_rrect(skiatest::Reporter* reporter,
244                              const SkRect& rect,
245                              SkScalar l, SkScalar t, SkScalar r, SkScalar b,
246                              bool checkRadii) {
247    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
248        for (int start = 0; start < 8; ++start) {
249            SkRRect rr;
250            rr.setNinePatch(rect, l, t, r, b);
251            if (checkRadii) {
252                path_contains_rrect_check(reporter, rr, dir, start);
253            } else {
254                path_contains_rrect_nocheck(reporter, rr, dir, start);
255            }
256
257            SkRRect rr2; // construct the same RR using the most general set function
258            SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
259            rr2.setRectRadii(rect, radii);
260            if (checkRadii) {
261                path_contains_rrect_check(reporter, rr, dir, start);
262            } else {
263                path_contains_rrect_nocheck(reporter, rr, dir, start);
264            }
265        }
266    }
267}
268
269// Test out the basic API entry points
270static void test_round_rect_basic(skiatest::Reporter* reporter) {
271    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
272        for (int start = 0; start < 8; ++start) {
273            //----
274            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
275
276            SkRRect rr1;
277            rr1.setRect(rect);
278            path_contains_rrect_check(reporter, rr1, dir, start);
279
280            SkRRect rr1_2; // construct the same RR using the most general set function
281            SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
282            rr1_2.setRectRadii(rect, rr1_2_radii);
283            path_contains_rrect_check(reporter, rr1_2, dir, start);
284            SkRRect rr1_3;  // construct the same RR using the nine patch set function
285            rr1_3.setNinePatch(rect, 0, 0, 0, 0);
286            path_contains_rrect_check(reporter, rr1_2, dir, start);
287
288            //----
289            SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
290            SkRRect rr2;
291            rr2.setOval(rect);
292            path_contains_rrect_check(reporter, rr2, dir, start);
293
294            SkRRect rr2_2;  // construct the same RR using the most general set function
295            SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY },
296                                        { halfPoint.fX, halfPoint.fY },
297                                        { halfPoint.fX, halfPoint.fY },
298                                        { halfPoint.fX, halfPoint.fY } };
299            rr2_2.setRectRadii(rect, rr2_2_radii);
300            path_contains_rrect_check(reporter, rr2_2, dir, start);
301            SkRRect rr2_3;  // construct the same RR using the nine patch set function
302            rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
303            path_contains_rrect_check(reporter, rr2_3, dir, start);
304
305            //----
306            SkPoint p = { 5, 5 };
307            SkRRect rr3;
308            rr3.setRectXY(rect, p.fX, p.fY);
309            path_contains_rrect_check(reporter, rr3, dir, start);
310
311            SkRRect rr3_2; // construct the same RR using the most general set function
312            SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
313            rr3_2.setRectRadii(rect, rr3_2_radii);
314            path_contains_rrect_check(reporter, rr3_2, dir, start);
315            SkRRect rr3_3;  // construct the same RR using the nine patch set function
316            rr3_3.setNinePatch(rect, 5, 5, 5, 5);
317            path_contains_rrect_check(reporter, rr3_3, dir, start);
318
319            //----
320            test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
321
322            {
323                // Test out the rrect from skia:3466
324                SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f,
325                                                0.806214333f);
326
327                test_9patch_rrect(reporter,
328                                  rect2,
329                                  0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
330                                  false);
331            }
332
333            //----
334            SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
335
336            SkRRect rr5;
337            rr5.setRectRadii(rect, radii2);
338            path_contains_rrect_check(reporter, rr5, dir, start);
339        }
340    }
341}
342
343// Test out the cases when the RR degenerates to a rect
344static void test_round_rect_rects(skiatest::Reporter* reporter) {
345    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
346        for (int start = 0; start < 8; ++start) {
347            //----
348            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
349            SkRRect rr1;
350            rr1.setRectXY(rect, 0, 0);
351
352            path_contains_rrect_check(reporter, rr1, dir, start);
353
354            //----
355            SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
356
357            SkRRect rr2;
358            rr2.setRectRadii(rect, radii);
359
360            path_contains_rrect_check(reporter, rr2, dir, start);
361
362            //----
363            SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
364
365            SkRRect rr3;
366            rr3.setRectRadii(rect, radii2);
367            path_contains_rrect_check(reporter, rr3, dir, start);
368        }
369    }
370}
371
372// Test out the cases when the RR degenerates to an oval
373static void test_round_rect_ovals(skiatest::Reporter* reporter) {
374    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
375        for (int start = 0; start < 8; ++start) {
376            //----
377            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
378            SkRRect rr1;
379            rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
380
381            path_contains_rrect_check(reporter, rr1, dir, start);
382        }
383    }
384}
385
386// Test out the non-degenerate RR cases
387static void test_round_rect_general(skiatest::Reporter* reporter) {
388    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
389        for (int start = 0; start < 8; ++start) {
390            //----
391            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
392            SkRRect rr1;
393            rr1.setRectXY(rect, 20, 20);
394
395            path_contains_rrect_check(reporter, rr1, dir, start);
396
397            //----
398            SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
399
400            SkRRect rr2;
401            rr2.setRectRadii(rect, radii);
402
403            path_contains_rrect_check(reporter, rr2, dir, start);
404        }
405    }
406}
407
408static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
409    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
410        for (int start = 0; start < 8; ++start) {
411            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
412            SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
413            SkRRect rr1;
414            rr1.setRectRadii(rect, radii);
415            path_contains_rrect_nocheck(reporter, rr1, dir, start);
416        }
417    }
418}
419
420static void set_radii(SkVector radii[4], int index, float rad) {
421    sk_bzero(radii, sizeof(SkVector) * 4);
422    radii[index].set(rad, rad);
423}
424
425static void test_skbug_3239(skiatest::Reporter* reporter) {
426    const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */
427    const float max = SkBits2Float(0x4b7f1c1d); /*  16718877.000000 */
428    const float big = SkBits2Float(0x4b7f1bd7); /*  16718807.000000 */
429
430    const float rad = 33436320;
431
432    const SkRect rectx = SkRect::MakeLTRB(min, min, max, big);
433    const SkRect recty = SkRect::MakeLTRB(min, min, big, max);
434
435    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
436        for (int start = 0; start < 8; ++start) {
437            SkVector radii[4];
438            for (int i = 0; i < 4; ++i) {
439                set_radii(radii, i, rad);
440                path_contains_rrect_check(reporter, rectx, radii, dir, start);
441                path_contains_rrect_check(reporter, recty, radii, dir, start);
442            }
443        }
444    }
445}
446
447static void test_mix(skiatest::Reporter* reporter) {
448    for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
449        for (int start = 0; start < 8; ++start) {
450            // Test out mixed degenerate and non-degenerate geometry with Conics
451            const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } };
452            SkRect r = SkRect::MakeWH(100, 100);
453            SkRRect rr;
454            rr.setRectRadii(r, radii);
455            path_contains_rrect_check(reporter, rr, dir, start);
456        }
457    }
458}
459
460DEF_TEST(RoundRectInPath, reporter) {
461    test_tricky_radii(reporter);
462    test_empty_crbug_458524(reporter);
463    test_inset(reporter);
464    test_round_rect_basic(reporter);
465    test_round_rect_rects(reporter);
466    test_round_rect_ovals(reporter);
467    test_round_rect_general(reporter);
468    test_undetected_paths(reporter);
469    test_round_rect_iffy_parameters(reporter);
470    test_skbug_3239(reporter);
471    test_mix(reporter);
472}
473