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 "include/core/SkMatrix.h"
9#include "include/core/SkPath.h"
10#include "include/core/SkRRect.h"
11#include "src/core/SkPathPriv.h"
12#include "tests/Test.h"
13
14static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path,
15                                   SkPathDirection* dir, unsigned* start) {
16    SkRRect out;
17    REPORTER_ASSERT(reporter, SkPathPriv::IsRRect(path, &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::Scale( 1,  1),
25        SkMatrix::Scale(-1,  1),
26        SkMatrix::Scale( 1, -1),
27        SkMatrix::Scale(-1, -1),
28    };
29    for (auto& m : kMatrices) {
30        SkPath xformed;
31        path.transform(m, &xformed);
32        SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty());
33        SkPathDirection xd = SkPathDirection::kCCW;
34        unsigned xs = ~0U;
35        REPORTER_ASSERT(reporter, SkPathPriv::IsRRect(xformed, &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                                         SkPathDirection 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    SkPathDirection 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                                      SkPathDirection dir, unsigned start) {
64    SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
65    if (in != out) {
66        SkDebugf("%s", "");
67    }
68    REPORTER_ASSERT(reporter, in == out);
69}
70
71static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in,
72                                        SkPathDirection dir, unsigned start) {
73    SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
74    if (in == out) {
75        SkDebugf("%s", "");
76    }
77}
78
79static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r,
80        SkVector v[4], SkPathDirection 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, SkPathDirection dir, unsigned start) {
89        path->fPathRef->setIsRRect(true, dir == SkPathDirection::kCCW, start);
90    }
91};
92
93static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path,
94                                      SkPathDirection dir, unsigned start) {
95    ForceIsRRect_Private force_rrect(&path, dir, start);
96    SkPathDirection 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 first 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 exactPath;
107    exactPath.addCircle(0, 0, 10);
108    REPORTER_ASSERT(reporter, SkPath::kMove_Verb == SkPathPriv::VerbData(exactPath)[0]);
109    REPORTER_ASSERT(reporter, SkPath::kConic_Verb == SkPathPriv::VerbData(exactPath)[1]);
110    const SkScalar weight = SkPathPriv::ConicWeightData(exactPath)[0];
111
112    SkPath path;
113    path.moveTo(0, 62.5f);
114    path.lineTo(0, 3.5f);
115    path.conicTo(0, 0, 3.5f, 0, weight);
116    path.lineTo(196.5f, 0);
117    path.conicTo(200, 0, 200, 3.5f, weight);
118    path.lineTo(200, 62.5f);
119    path.conicTo(200, 66, 196.5f, 66, weight);
120    path.lineTo(3.5f, 66);
121    path.conicTo(0, 66, 0, 62.5, weight);
122    path.close();
123    force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
124
125    path.reset();
126    path.moveTo(0, 81.5f);
127    path.lineTo(0, 3.5f);
128    path.conicTo(0, 0, 3.5f, 0, weight);
129    path.lineTo(149.5, 0);
130    path.conicTo(153, 0, 153, 3.5f, weight);
131    path.lineTo(153, 81.5f);
132    path.conicTo(153, 85, 149.5f, 85, weight);
133    path.lineTo(3.5f, 85);
134    path.conicTo(0, 85, 0, 81.5f, weight);
135    path.close();
136    force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
137
138    path.reset();
139    path.moveTo(14, 1189);
140    path.lineTo(14, 21);
141    path.conicTo(14, 14, 21, 14, weight);
142    path.lineTo(1363, 14);
143    path.conicTo(1370, 14, 1370, 21, weight);
144    path.lineTo(1370, 1189);
145    path.conicTo(1370, 1196, 1363, 1196, weight);
146    path.lineTo(21, 1196);
147    path.conicTo(14, 1196, 14, 1189, weight);
148    path.close();
149    force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
150
151    path.reset();
152    path.moveTo(14, 1743);
153    path.lineTo(14, 21);
154    path.conicTo(14, 14, 21, 14, weight);
155    path.lineTo(1363, 14);
156    path.conicTo(1370, 14, 1370, 21, weight);
157    path.lineTo(1370, 1743);
158    path.conicTo(1370, 1750, 1363, 1750, weight);
159    path.lineTo(21, 1750);
160    path.conicTo(14, 1750, 14, 1743, weight);
161    path.close();
162    force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
163}
164
165static const SkScalar kWidth = 100.0f;
166static const SkScalar kHeight = 100.0f;
167
168static void test_tricky_radii(skiatest::Reporter* reporter) {
169    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
170        for (int start = 0; start < 8; ++start) {
171            {
172                // crbug.com/458522
173                SkRRect rr;
174                const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
175                const SkScalar rad = 12814;
176                const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
177                rr.setRectRadii(bounds, vec);
178                path_contains_rrect_check(reporter, rr, dir, start);
179            }
180
181            {
182                // crbug.com//463920
183                SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
184                SkVector radii[4] = {
185                    { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
186                };
187                SkRRect rr;
188                rr.setRectRadii(r, radii);
189                path_contains_rrect_nocheck(reporter, rr, dir, start);
190            }
191        }
192    }
193}
194
195static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
196    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
197        for (int start = 0; start < 8; ++start) {
198            SkRRect rr;
199            const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
200            const SkScalar rad = 40;
201            rr.setRectXY(bounds, rad, rad);
202            path_contains_rrect_check(reporter, rr, dir, start);
203
204            SkRRect other;
205            SkMatrix matrix;
206            matrix.setScale(0, 1);
207            rr.transform(matrix, &other);
208            path_contains_rrect_check(reporter, rr, dir, start);
209        }
210    }
211}
212
213static void test_inset(skiatest::Reporter* reporter) {
214    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
215        for (int start = 0; start < 8; ++start) {
216            SkRRect rr, rr2;
217            SkRect r = { 0, 0, 100, 100 };
218
219            rr.setRect(r);
220            rr.inset(-20, -20, &rr2);
221            path_contains_rrect_check(reporter, rr, dir, start);
222
223            rr.inset(20, 20, &rr2);
224            path_contains_rrect_check(reporter, rr, dir, start);
225
226            rr.inset(r.width()/2, r.height()/2, &rr2);
227            path_contains_rrect_check(reporter, rr, dir, start);
228
229            rr.setRectXY(r, 20, 20);
230            rr.inset(19, 19, &rr2);
231            path_contains_rrect_check(reporter, rr, dir, start);
232            rr.inset(20, 20, &rr2);
233            path_contains_rrect_check(reporter, rr, dir, start);
234        }
235    }
236}
237
238
239static void test_9patch_rrect(skiatest::Reporter* reporter,
240                              const SkRect& rect,
241                              SkScalar l, SkScalar t, SkScalar r, SkScalar b,
242                              bool checkRadii) {
243    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
244        for (int start = 0; start < 8; ++start) {
245            SkRRect rr;
246            rr.setNinePatch(rect, l, t, r, b);
247            if (checkRadii) {
248                path_contains_rrect_check(reporter, rr, dir, start);
249            } else {
250                path_contains_rrect_nocheck(reporter, rr, dir, start);
251            }
252
253            SkRRect rr2; // construct the same RR using the most general set function
254            SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
255            rr2.setRectRadii(rect, radii);
256            if (checkRadii) {
257                path_contains_rrect_check(reporter, rr, dir, start);
258            } else {
259                path_contains_rrect_nocheck(reporter, rr, dir, start);
260            }
261        }
262    }
263}
264
265// Test out the basic API entry points
266static void test_round_rect_basic(skiatest::Reporter* reporter) {
267    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
268        for (int start = 0; start < 8; ++start) {
269            //----
270            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
271
272            SkRRect rr1;
273            rr1.setRect(rect);
274            path_contains_rrect_check(reporter, rr1, dir, start);
275
276            SkRRect rr1_2; // construct the same RR using the most general set function
277            SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
278            rr1_2.setRectRadii(rect, rr1_2_radii);
279            path_contains_rrect_check(reporter, rr1_2, dir, start);
280            SkRRect rr1_3;  // construct the same RR using the nine patch set function
281            rr1_3.setNinePatch(rect, 0, 0, 0, 0);
282            path_contains_rrect_check(reporter, rr1_2, dir, start);
283
284            //----
285            SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
286            SkRRect rr2;
287            rr2.setOval(rect);
288            path_contains_rrect_check(reporter, rr2, dir, start);
289
290            SkRRect rr2_2;  // construct the same RR using the most general set function
291            SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY },
292                                        { halfPoint.fX, halfPoint.fY },
293                                        { halfPoint.fX, halfPoint.fY },
294                                        { halfPoint.fX, halfPoint.fY } };
295            rr2_2.setRectRadii(rect, rr2_2_radii);
296            path_contains_rrect_check(reporter, rr2_2, dir, start);
297            SkRRect rr2_3;  // construct the same RR using the nine patch set function
298            rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
299            path_contains_rrect_check(reporter, rr2_3, dir, start);
300
301            //----
302            SkPoint p = { 5, 5 };
303            SkRRect rr3;
304            rr3.setRectXY(rect, p.fX, p.fY);
305            path_contains_rrect_check(reporter, rr3, dir, start);
306
307            SkRRect rr3_2; // construct the same RR using the most general set function
308            SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
309            rr3_2.setRectRadii(rect, rr3_2_radii);
310            path_contains_rrect_check(reporter, rr3_2, dir, start);
311            SkRRect rr3_3;  // construct the same RR using the nine patch set function
312            rr3_3.setNinePatch(rect, 5, 5, 5, 5);
313            path_contains_rrect_check(reporter, rr3_3, dir, start);
314
315            //----
316            test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
317
318            {
319                // Test out the rrect from skia:3466
320                SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f,
321                                                0.806214333f);
322
323                test_9patch_rrect(reporter,
324                                  rect2,
325                                  0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
326                                  false);
327            }
328
329            //----
330            SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
331
332            SkRRect rr5;
333            rr5.setRectRadii(rect, radii2);
334            path_contains_rrect_check(reporter, rr5, dir, start);
335        }
336    }
337}
338
339// Test out the cases when the RR degenerates to a rect
340static void test_round_rect_rects(skiatest::Reporter* reporter) {
341    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
342        for (int start = 0; start < 8; ++start) {
343            //----
344            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
345            SkRRect rr1;
346            rr1.setRectXY(rect, 0, 0);
347
348            path_contains_rrect_check(reporter, rr1, dir, start);
349
350            //----
351            SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
352
353            SkRRect rr2;
354            rr2.setRectRadii(rect, radii);
355
356            path_contains_rrect_check(reporter, rr2, dir, start);
357
358            //----
359            SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
360
361            SkRRect rr3;
362            rr3.setRectRadii(rect, radii2);
363            path_contains_rrect_check(reporter, rr3, dir, start);
364        }
365    }
366}
367
368// Test out the cases when the RR degenerates to an oval
369static void test_round_rect_ovals(skiatest::Reporter* reporter) {
370    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
371        for (int start = 0; start < 8; ++start) {
372            //----
373            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
374            SkRRect rr1;
375            rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
376
377            path_contains_rrect_check(reporter, rr1, dir, start);
378        }
379    }
380}
381
382// Test out the non-degenerate RR cases
383static void test_round_rect_general(skiatest::Reporter* reporter) {
384    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
385        for (int start = 0; start < 8; ++start) {
386            //----
387            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
388            SkRRect rr1;
389            rr1.setRectXY(rect, 20, 20);
390
391            path_contains_rrect_check(reporter, rr1, dir, start);
392
393            //----
394            SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
395
396            SkRRect rr2;
397            rr2.setRectRadii(rect, radii);
398
399            path_contains_rrect_check(reporter, rr2, dir, start);
400        }
401    }
402}
403
404static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
405    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
406        for (int start = 0; start < 8; ++start) {
407            SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
408            SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
409            SkRRect rr1;
410            rr1.setRectRadii(rect, radii);
411            path_contains_rrect_nocheck(reporter, rr1, dir, start);
412        }
413    }
414}
415
416static void set_radii(SkVector radii[4], int index, float rad) {
417    sk_bzero(radii, sizeof(SkVector) * 4);
418    radii[index].set(rad, rad);
419}
420
421static void test_skbug_3239(skiatest::Reporter* reporter) {
422    const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */
423    const float max = SkBits2Float(0x4b7f1c1d); /*  16718877.000000 */
424    const float big = SkBits2Float(0x4b7f1bd7); /*  16718807.000000 */
425
426    const float rad = 33436320;
427
428    const SkRect rectx = SkRect::MakeLTRB(min, min, max, big);
429    const SkRect recty = SkRect::MakeLTRB(min, min, big, max);
430
431    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
432        for (int start = 0; start < 8; ++start) {
433            SkVector radii[4];
434            for (int i = 0; i < 4; ++i) {
435                set_radii(radii, i, rad);
436                path_contains_rrect_check(reporter, rectx, radii, dir, start);
437                path_contains_rrect_check(reporter, recty, radii, dir, start);
438            }
439        }
440    }
441}
442
443static void test_mix(skiatest::Reporter* reporter) {
444    for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
445        for (int start = 0; start < 8; ++start) {
446            // Test out mixed degenerate and non-degenerate geometry with Conics
447            const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } };
448            SkRect r = SkRect::MakeWH(100, 100);
449            SkRRect rr;
450            rr.setRectRadii(r, radii);
451            path_contains_rrect_check(reporter, rr, dir, start);
452        }
453    }
454}
455
456DEF_TEST(RoundRectInPath, reporter) {
457    test_tricky_radii(reporter);
458    test_empty_crbug_458524(reporter);
459    test_inset(reporter);
460    test_round_rect_basic(reporter);
461    test_round_rect_rects(reporter);
462    test_round_rect_ovals(reporter);
463    test_round_rect_general(reporter);
464    test_undetected_paths(reporter);
465    test_round_rect_iffy_parameters(reporter);
466    test_skbug_3239(reporter);
467    test_mix(reporter);
468}
469
470DEF_TEST(RRect_fragile, reporter) {
471    SkRect rect = {
472        SkBits2Float(0x1f800000),  // 0x003F0000 was the starter value that also fails
473        SkBits2Float(0x1400001C),
474        SkBits2Float(0x3F000004),
475        SkBits2Float(0x3F000004),
476    };
477
478    SkPoint radii[] = {
479        { SkBits2Float(0x00000001), SkBits2Float(0x00000001) },
480        { SkBits2Float(0x00000020), SkBits2Float(0x00000001) },
481        { SkBits2Float(0x00000000), SkBits2Float(0x00000000) },
482        { SkBits2Float(0x3F000004), SkBits2Float(0x3F000004) },
483    };
484
485    SkRRect rr;
486    // please don't assert
487    if (false) {    // disable until we fix this
488        SkDebugf("%g 0x%08X\n", rect.fLeft, SkFloat2Bits(rect.fLeft));
489        rr.setRectRadii(rect, radii);
490    }
491}
492
493