• 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 "SkMatrix.h"
9 #include "SkPath.h"
10 #include "SkPathPriv.h"
11 #include "SkRRect.h"
12 #include "Test.h"
13 
path_contains_rrect(skiatest::Reporter * reporter,const SkPath & path,SkPath::Direction * dir,unsigned * start)14 static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path,
15                                    SkPath::Direction* 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::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, 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,SkPath::Direction dir,unsigned start)43 static 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 
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRRect & in,SkPath::Direction dir,unsigned start)62 static 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 
path_contains_rrect_nocheck(skiatest::Reporter * reporter,const SkRRect & in,SkPath::Direction dir,unsigned start)71 static 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 
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRect & r,SkVector v[4],SkPath::Direction dir,unsigned start)79 static 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 
86 class ForceIsRRect_Private {
87 public:
ForceIsRRect_Private(SkPath * path,SkPath::Direction dir,unsigned start)88     ForceIsRRect_Private(SkPath* path, SkPath::Direction dir, unsigned start) {
89         path->fPathRef->setIsRRect(true, dir == SkPath::kCCW_Direction, start);
90     }
91 };
92 
force_path_contains_rrect(skiatest::Reporter * reporter,SkPath & path,SkPath::Direction dir,unsigned start)93 static 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 
test_undetected_paths(skiatest::Reporter * reporter)102 static 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 
169 static const SkScalar kWidth = 100.0f;
170 static const SkScalar kHeight = 100.0f;
171 
test_tricky_radii(skiatest::Reporter * reporter)172 static 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 
test_empty_crbug_458524(skiatest::Reporter * reporter)199 static 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 
test_inset(skiatest::Reporter * reporter)217 static 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 
test_9patch_rrect(skiatest::Reporter * reporter,const SkRect & rect,SkScalar l,SkScalar t,SkScalar r,SkScalar b,bool checkRadii)243 static 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
test_round_rect_basic(skiatest::Reporter * reporter)270 static 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
test_round_rect_rects(skiatest::Reporter * reporter)344 static 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
test_round_rect_ovals(skiatest::Reporter * reporter)373 static 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
test_round_rect_general(skiatest::Reporter * reporter)387 static 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 
test_round_rect_iffy_parameters(skiatest::Reporter * reporter)408 static 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 
set_radii(SkVector radii[4],int index,float rad)420 static 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 
test_skbug_3239(skiatest::Reporter * reporter)425 static 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 
test_mix(skiatest::Reporter * reporter)447 static 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 
DEF_TEST(RoundRectInPath,reporter)460 DEF_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 
DEF_TEST(RRect_fragile,reporter)474 DEF_TEST(RRect_fragile, reporter) {
475     SkRect rect = {
476         SkBits2Float(0x1f800000),  // 0x003F0000 was the starter value that also fails
477         SkBits2Float(0x1400001C),
478         SkBits2Float(0x3F000004),
479         SkBits2Float(0x3F000004),
480     };
481 
482     SkPoint radii[] = {
483         { SkBits2Float(0x00000001), SkBits2Float(0x00000001) },
484         { SkBits2Float(0x00000020), SkBits2Float(0x00000001) },
485         { SkBits2Float(0x00000000), SkBits2Float(0x00000000) },
486         { SkBits2Float(0x3F000004), SkBits2Float(0x3F000004) },
487     };
488 
489     SkRRect rr;
490     // please don't assert
491     if (false) {    // disable until we fix this
492         SkDebugf("%g 0x%08X\n", rect.fLeft, SkFloat2Bits(rect.fLeft));
493         rr.setRectRadii(rect, radii);
494     }
495 }
496 
497