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 "SkPathOps.h"
12#include "SkRRect.h"
13#include "Test.h"
14
15static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path) {
16    SkRRect out;
17    REPORTER_ASSERT(reporter, path.isRRect(&out));
18    SkPath path2, xorBoth;
19    path2.addRRect(out);
20    if (path == path2) {
21        return out;
22    }
23    Op(path, path2, SkPathOp::kXOR_SkPathOp, &xorBoth);
24    REPORTER_ASSERT(reporter, xorBoth.isEmpty());
25    return out;
26}
27
28static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in) {
29    switch (in.getType()) {
30        case SkRRect::kEmpty_Type:
31        case SkRRect::kRect_Type:
32        case SkRRect::kOval_Type:
33            return in;
34        default:
35            break;
36    }
37    SkPath path;
38    path.addRRect(in);
39    return path_contains_rrect(reporter, path);
40}
41
42static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in) {
43    SkRRect out = inner_path_contains_rrect(reporter, in);
44    if (in != out) {
45        SkDebugf("");
46    }
47    REPORTER_ASSERT(reporter, in == out);
48}
49
50static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in) {
51    SkRRect out = inner_path_contains_rrect(reporter, in);
52    if (in == out) {
53        SkDebugf("");
54    }
55}
56
57static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r,
58        SkVector v[4]) {
59    SkRRect rrect;
60    rrect.setRectRadii(r, v);
61    path_contains_rrect_check(reporter, rrect);
62}
63
64class ForceIsRRect_Private {
65public:
66    ForceIsRRect_Private(SkPath* path) {
67        path->fPathRef->setIsRRect(true);
68    }
69};
70
71static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path) {
72    ForceIsRRect_Private force_rrect(&path);
73    path_contains_rrect(reporter, path);
74}
75
76static void test_undetected_paths(skiatest::Reporter* reporter) {
77    SkPath path;
78    path.moveTo(0, 62.5f);
79    path.lineTo(0, 3.5f);
80    path.conicTo(0, 0, 3.5f, 0, 0.70710677f);
81    path.lineTo(196.5f, 0);
82    path.conicTo(200, 0, 200, 3.5f, 0.70710677f);
83    path.lineTo(200, 62.5f);
84    path.conicTo(200, 66, 196.5f, 66, 0.70710677f);
85    path.lineTo(3.5f, 66);
86    path.conicTo(0, 66, 0, 62.5, 0.70710677f);
87    path.close();
88    force_path_contains_rrect(reporter, path);
89
90    path.reset();
91    path.moveTo(0, 81.5f);
92    path.lineTo(0, 3.5f);
93    path.conicTo(0, 0, 3.5f, 0, 0.70710677f);
94    path.lineTo(149.5, 0);
95    path.conicTo(153, 0, 153, 3.5f, 0.70710677f);
96    path.lineTo(153, 81.5f);
97    path.conicTo(153, 85, 149.5f, 85, 0.70710677f);
98    path.lineTo(3.5f, 85);
99    path.conicTo(0, 85, 0, 81.5f, 0.70710677f);
100    path.close();
101    force_path_contains_rrect(reporter, path);
102
103    path.reset();
104    path.moveTo(14, 1189);
105    path.lineTo(14, 21);
106    path.conicTo(14, 14, 21, 14, 0.70710677f);
107    path.lineTo(1363, 14);
108    path.conicTo(1370, 14, 1370, 21, 0.70710677f);
109    path.lineTo(1370, 1189);
110    path.conicTo(1370, 1196, 1363, 1196, 0.70710677f);
111    path.lineTo(21, 1196);
112    path.conicTo(14, 1196, 14, 1189, 0.70710677f);
113    path.close();
114    force_path_contains_rrect(reporter, path);
115
116    path.reset();
117    path.moveTo(14, 1743);
118    path.lineTo(14, 21);
119    path.conicTo(14, 14, 21, 14, 0.70710677f);
120    path.lineTo(1363, 14);
121    path.conicTo(1370, 14, 1370, 21, 0.70710677f);
122    path.lineTo(1370, 1743);
123    path.conicTo(1370, 1750, 1363, 1750, 0.70710677f);
124    path.lineTo(21, 1750);
125    path.conicTo(14, 1750, 14, 1743, 0.70710677f);
126    path.close();
127    force_path_contains_rrect(reporter, path);
128}
129
130static const SkScalar kWidth = 100.0f;
131static const SkScalar kHeight = 100.0f;
132
133static void test_tricky_radii(skiatest::Reporter* reporter) {
134    {
135        // crbug.com/458522
136        SkRRect rr;
137        const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
138        const SkScalar rad = 12814;
139        const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
140        rr.setRectRadii(bounds, vec);
141        path_contains_rrect_check(reporter, rr);
142    }
143
144    {
145        // crbug.com//463920
146        SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
147        SkVector radii[4] = {
148            { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
149        };
150        SkRRect rr;
151        rr.setRectRadii(r, radii);
152        path_contains_rrect_nocheck(reporter, rr);
153    }
154}
155
156static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
157    SkRRect rr;
158    const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
159    const SkScalar rad = 40;
160    rr.setRectXY(bounds, rad, rad);
161    path_contains_rrect_check(reporter, rr);
162
163    SkRRect other;
164    SkMatrix matrix;
165    matrix.setScale(0, 1);
166    rr.transform(matrix, &other);
167    path_contains_rrect_check(reporter, rr);
168}
169
170static void test_inset(skiatest::Reporter* reporter) {
171    SkRRect rr, rr2;
172    SkRect r = { 0, 0, 100, 100 };
173
174    rr.setRect(r);
175    rr.inset(-20, -20, &rr2);
176    path_contains_rrect_check(reporter, rr);
177
178    rr.inset(20, 20, &rr2);
179    path_contains_rrect_check(reporter, rr);
180
181    rr.inset(r.width()/2, r.height()/2, &rr2);
182    path_contains_rrect_check(reporter, rr);
183
184    rr.setRectXY(r, 20, 20);
185    rr.inset(19, 19, &rr2);
186    path_contains_rrect_check(reporter, rr);
187    rr.inset(20, 20, &rr2);
188    path_contains_rrect_check(reporter, rr);
189}
190
191
192static void test_9patch_rrect(skiatest::Reporter* reporter,
193                              const SkRect& rect,
194                              SkScalar l, SkScalar t, SkScalar r, SkScalar b,
195                              bool checkRadii) {
196    SkRRect rr;
197    rr.setNinePatch(rect, l, t, r, b);
198    if (checkRadii) {
199        path_contains_rrect_check(reporter, rr);
200    } else {
201        path_contains_rrect_nocheck(reporter, rr);
202    }
203
204    SkRRect rr2; // construct the same RR using the most general set function
205    SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
206    rr2.setRectRadii(rect, radii);
207    if (checkRadii) {
208        path_contains_rrect_check(reporter, rr);
209    } else {
210        path_contains_rrect_nocheck(reporter, rr);
211    }
212}
213
214// Test out the basic API entry points
215static void test_round_rect_basic(skiatest::Reporter* reporter) {
216
217    //----
218    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
219
220    SkRRect rr1;
221    rr1.setRect(rect);
222    path_contains_rrect_check(reporter, rr1);
223
224    SkRRect rr1_2; // construct the same RR using the most general set function
225    SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
226    rr1_2.setRectRadii(rect, rr1_2_radii);
227    path_contains_rrect_check(reporter, rr1_2);
228    SkRRect rr1_3;  // construct the same RR using the nine patch set function
229    rr1_3.setNinePatch(rect, 0, 0, 0, 0);
230    path_contains_rrect_check(reporter, rr1_2);
231
232    //----
233    SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
234    SkRRect rr2;
235    rr2.setOval(rect);
236    path_contains_rrect_check(reporter, rr2);
237
238    SkRRect rr2_2;  // construct the same RR using the most general set function
239    SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY },
240                                { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY } };
241    rr2_2.setRectRadii(rect, rr2_2_radii);
242    path_contains_rrect_check(reporter, rr2_2);
243    SkRRect rr2_3;  // construct the same RR using the nine patch set function
244    rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
245    path_contains_rrect_check(reporter, rr2_3);
246
247    //----
248    SkPoint p = { 5, 5 };
249    SkRRect rr3;
250    rr3.setRectXY(rect, p.fX, p.fY);
251    path_contains_rrect_check(reporter, rr3);
252
253    SkRRect rr3_2; // construct the same RR using the most general set function
254    SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
255    rr3_2.setRectRadii(rect, rr3_2_radii);
256    path_contains_rrect_check(reporter, rr3_2);
257    SkRRect rr3_3;  // construct the same RR using the nine patch set function
258    rr3_3.setNinePatch(rect, 5, 5, 5, 5);
259    path_contains_rrect_check(reporter, rr3_3);
260
261    //----
262    test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
263
264    {
265        // Test out the rrect from skia:3466
266        SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 0.806214333f);
267
268        test_9patch_rrect(reporter,
269                          rect2,
270                          0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
271                          false);
272    }
273
274    //----
275    SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
276
277    SkRRect rr5;
278    rr5.setRectRadii(rect, radii2);
279    path_contains_rrect_check(reporter, rr5);
280}
281
282// Test out the cases when the RR degenerates to a rect
283static void test_round_rect_rects(skiatest::Reporter* reporter) {
284
285    //----
286    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
287    SkRRect rr1;
288    rr1.setRectXY(rect, 0, 0);
289
290    path_contains_rrect_check(reporter, rr1);
291
292    //----
293    SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
294
295    SkRRect rr2;
296    rr2.setRectRadii(rect, radii);
297
298    path_contains_rrect_check(reporter, rr2);
299
300    //----
301    SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
302
303    SkRRect rr3;
304    rr3.setRectRadii(rect, radii2);
305    path_contains_rrect_check(reporter, rr3);
306}
307
308// Test out the cases when the RR degenerates to an oval
309static void test_round_rect_ovals(skiatest::Reporter* reporter) {
310    //----
311    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
312    SkRRect rr1;
313    rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
314
315    path_contains_rrect_check(reporter, rr1);
316}
317
318// Test out the non-degenerate RR cases
319static void test_round_rect_general(skiatest::Reporter* reporter) {
320    //----
321    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
322    SkRRect rr1;
323    rr1.setRectXY(rect, 20, 20);
324
325    path_contains_rrect_check(reporter, rr1);
326
327    //----
328    SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
329
330    SkRRect rr2;
331    rr2.setRectRadii(rect, radii);
332
333    path_contains_rrect_check(reporter, rr2);
334}
335
336static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
337    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
338    SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
339    SkRRect rr1;
340    rr1.setRectRadii(rect, radii);
341    path_contains_rrect_nocheck(reporter, rr1);
342}
343
344static void set_radii(SkVector radii[4], int index, float rad) {
345    sk_bzero(radii, sizeof(SkVector) * 4);
346    radii[index].set(rad, rad);
347}
348
349static void test_skbug_3239(skiatest::Reporter* reporter) {
350    const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */
351    const float max = SkBits2Float(0x4b7f1c1d); /*  16718877.000000 */
352    const float big = SkBits2Float(0x4b7f1bd7); /*  16718807.000000 */
353
354    const float rad = 33436320;
355
356    const SkRect rectx = SkRect::MakeLTRB(min, min, max, big);
357    const SkRect recty = SkRect::MakeLTRB(min, min, big, max);
358
359    SkVector radii[4];
360    for (int i = 0; i < 4; ++i) {
361        set_radii(radii, i, rad);
362        path_contains_rrect_check(reporter, rectx, radii);
363        path_contains_rrect_check(reporter, recty, radii);
364    }
365}
366
367static void test_mix(skiatest::Reporter* reporter) {
368    // Test out mixed degenerate and non-degenerate geometry with Conics
369    const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } };
370    SkRect r = SkRect::MakeWH(100, 100);
371    SkRRect rr;
372    rr.setRectRadii(r, radii);
373    path_contains_rrect_check(reporter, rr);
374}
375
376DEF_TEST(RoundRectInPath, reporter) {
377    test_tricky_radii(reporter);
378    test_empty_crbug_458524(reporter);
379    test_inset(reporter);
380    test_round_rect_basic(reporter);
381    test_round_rect_rects(reporter);
382    test_round_rect_ovals(reporter);
383    test_round_rect_general(reporter);
384    test_undetected_paths(reporter);
385    test_round_rect_iffy_parameters(reporter);
386    test_skbug_3239(reporter);
387    test_mix(reporter);
388}
389