• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
path_contains_rrect(skiatest::Reporter * reporter,const SkPath & path,SkPathDirection * dir,unsigned * start)14 static 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 
inner_path_contains_rrect(skiatest::Reporter * reporter,const SkRRect & in,SkPathDirection dir,unsigned start)43 static 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 
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRRect & in,SkPathDirection dir,unsigned start)62 static 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 
path_contains_rrect_nocheck(skiatest::Reporter * reporter,const SkRRect & in,SkPathDirection dir,unsigned start)71 static 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 
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRect & r,SkVector v[4],SkPathDirection dir,unsigned start)79 static 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 
86 class ForceIsRRect_Private {
87 public:
ForceIsRRect_Private(SkPath * path,SkPathDirection dir,unsigned start)88     ForceIsRRect_Private(SkPath* path, SkPathDirection dir, unsigned start) {
89         path->fPathRef->setIsRRect(true, dir == SkPathDirection::kCCW, start);
90     }
91 };
92 
force_path_contains_rrect(skiatest::Reporter * reporter,SkPath & path,SkPathDirection dir,unsigned start)93 static 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 
test_undetected_paths(skiatest::Reporter * reporter)102 static 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 
165 static const SkScalar kWidth = 100.0f;
166 static const SkScalar kHeight = 100.0f;
167 
test_tricky_radii(skiatest::Reporter * reporter)168 static 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 
test_empty_crbug_458524(skiatest::Reporter * reporter)195 static 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 
test_inset(skiatest::Reporter * reporter)213 static 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 
test_9patch_rrect(skiatest::Reporter * reporter,const SkRect & rect,SkScalar l,SkScalar t,SkScalar r,SkScalar b,bool checkRadii)239 static 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
test_round_rect_basic(skiatest::Reporter * reporter)266 static 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
test_round_rect_rects(skiatest::Reporter * reporter)340 static 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
test_round_rect_ovals(skiatest::Reporter * reporter)369 static 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
test_round_rect_general(skiatest::Reporter * reporter)383 static 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 
test_round_rect_iffy_parameters(skiatest::Reporter * reporter)404 static 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 
set_radii(SkVector radii[4],int index,float rad)416 static 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 
test_skbug_3239(skiatest::Reporter * reporter)421 static 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 
test_mix(skiatest::Reporter * reporter)443 static 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 
DEF_TEST(RoundRectInPath,reporter)456 DEF_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 
DEF_TEST(RRect_fragile,reporter)470 DEF_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