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 
path_contains_rrect(skiatest::Reporter * reporter,const SkPath & path)15 static 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 
inner_path_contains_rrect(skiatest::Reporter * reporter,const SkRRect & in)28 static 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 
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRRect & in)42 static 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 
path_contains_rrect_nocheck(skiatest::Reporter * reporter,const SkRRect & in)50 static 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 
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRect & r,SkVector v[4])57 static 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 
64 class ForceIsRRect_Private {
65 public:
ForceIsRRect_Private(SkPath * path)66     ForceIsRRect_Private(SkPath* path) {
67         path->fPathRef->setIsRRect(true);
68     }
69 };
70 
force_path_contains_rrect(skiatest::Reporter * reporter,SkPath & path)71 static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path) {
72     ForceIsRRect_Private force_rrect(&path);
73     path_contains_rrect(reporter, path);
74 }
75 
test_undetected_paths(skiatest::Reporter * reporter)76 static 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 
130 static const SkScalar kWidth = 100.0f;
131 static const SkScalar kHeight = 100.0f;
132 
test_tricky_radii(skiatest::Reporter * reporter)133 static 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 
test_empty_crbug_458524(skiatest::Reporter * reporter)156 static 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 
test_inset(skiatest::Reporter * reporter)170 static 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 
test_9patch_rrect(skiatest::Reporter * reporter,const SkRect & rect,SkScalar l,SkScalar t,SkScalar r,SkScalar b,bool checkRadii)192 static 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
test_round_rect_basic(skiatest::Reporter * reporter)215 static 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
test_round_rect_rects(skiatest::Reporter * reporter)283 static 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
test_round_rect_ovals(skiatest::Reporter * reporter)309 static 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
test_round_rect_general(skiatest::Reporter * reporter)319 static 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 
test_round_rect_iffy_parameters(skiatest::Reporter * reporter)336 static 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 
set_radii(SkVector radii[4],int index,float rad)344 static 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 
test_skbug_3239(skiatest::Reporter * reporter)349 static 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 
test_mix(skiatest::Reporter * reporter)367 static 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 
DEF_TEST(RoundRectInPath,reporter)376 DEF_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