• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011 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/SkContourMeasure.h"
9 #include "include/core/SkPath.h"
10 #include "include/core/SkPathMeasure.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRefCnt.h"
13 #include "include/core/SkScalar.h"
14 #include "include/core/SkTypes.h"
15 #include "src/core/SkPathMeasurePriv.h"
16 #include "src/core/SkPathPriv.h"
17 #include "tests/Test.h"
18 
19 #include <array>
20 #include <cstddef>
21 #include <initializer_list>
22 #include <utility>
23 
test_small_segment3(skiatest::Reporter * reporter)24 static void test_small_segment3(skiatest::Reporter* reporter) {
25     SkPath path;
26     const SkPoint pts[] = {
27         { 0, 0 },
28         { 100000000000.0f, 100000000000.0f }, { 0, 0 }, { 10, 10 },
29         { 10, 10 }, { 0, 0 }, { 10, 10 }
30     };
31 
32     path.moveTo(pts[0]);
33     for (size_t i = 1; i < std::size(pts); i += 3) {
34         path.cubicTo(pts[i], pts[i + 1], pts[i + 2]);
35     }
36 
37     SkPathMeasure meas(path, false);
38     meas.getLength();
39 
40     // Now check that we cap the segment size even with very large resolution scales.
41     // Earlier versions allowed the pathmeasure to recurse without limit in the face
42     // of a very large scale.
43     //
44     //  Before this limit, the above meas had 15K segments, and when built with
45     // a resScale of 100, it had 184K segments -- for 1 cubic!
46     {
47         auto n = SkPathMeasurePriv::CountSegments(meas);
48         REPORTER_ASSERT(reporter, n < 300);
49 
50         constexpr float resScale = 1000;
51         n = SkPathMeasurePriv::CountSegments(SkPathMeasure(path, false, resScale));
52         REPORTER_ASSERT(reporter, n < 300);
53     }
54 }
55 
test_small_segment2()56 static void test_small_segment2() {
57     SkPath path;
58     const SkPoint pts[] = {
59         { 0, 0 },
60         { 100000000000.0f, 100000000000.0f }, { 0, 0 },
61         { 10, 10 }, { 0, 0 },
62     };
63 
64     path.moveTo(pts[0]);
65     for (size_t i = 1; i < std::size(pts); i += 2) {
66         path.quadTo(pts[i], pts[i + 1]);
67     }
68     SkPathMeasure meas(path, false);
69     meas.getLength();
70 }
71 
test_small_segment()72 static void test_small_segment() {
73     SkPath path;
74     const SkPoint pts[] = {
75         { 100000, 100000},
76         // big jump between these points, makes a big segment
77         { 1.0005f, 0.9999f },
78         // tiny (non-zero) jump between these points
79         { SK_Scalar1, SK_Scalar1 },
80     };
81 
82     path.moveTo(pts[0]);
83     for (size_t i = 1; i < std::size(pts); ++i) {
84         path.lineTo(pts[i]);
85     }
86     SkPathMeasure meas(path, false);
87 
88     /*  this would assert (before a fix) because we added a segment with
89         the same length as the prev segment, due to the follow (bad) pattern
90 
91         d = distance(pts[0], pts[1]);
92         distance += d;
93         seg->fDistance = distance;
94 
95         SkASSERT(d > 0);    // TRUE
96         SkASSERT(seg->fDistance > prevSeg->fDistance);  // FALSE
97 
98         This 2nd assert failes because (distance += d) didn't affect distance
99         because distance >>> d.
100      */
101     meas.getLength();
102 }
103 
DEF_TEST(PathMeasure,reporter)104 DEF_TEST(PathMeasure, reporter) {
105     SkPath  path;
106 
107     path.moveTo(0, 0);
108     path.lineTo(SK_Scalar1, 0);
109     path.lineTo(SK_Scalar1, SK_Scalar1);
110     path.lineTo(0, SK_Scalar1);
111 
112     SkPathMeasure   meas(path, true);
113     SkScalar        length = meas.getLength();
114     SkASSERT(length == SK_Scalar1*4);
115 
116     path.reset();
117     path.moveTo(0, 0);
118     path.lineTo(SK_Scalar1*3, SK_Scalar1*4);
119     meas.setPath(&path, false);
120     length = meas.getLength();
121     REPORTER_ASSERT(reporter, length == SK_Scalar1*5);
122 
123     path.reset();
124     path.addCircle(0, 0, SK_Scalar1);
125     meas.setPath(&path, true);
126     length = meas.getLength();
127 //    SkDebugf("circle arc-length = %g\n", length);
128 
129     // Test the behavior following a close not followed by a move.
130     path.reset();
131     path.lineTo(SK_Scalar1, 0);
132     path.lineTo(SK_Scalar1, SK_Scalar1);
133     path.lineTo(0, SK_Scalar1);
134     path.close();
135     path.lineTo(-SK_Scalar1, 0);
136     meas.setPath(&path, false);
137     length = meas.getLength();
138     REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4);
139     meas.nextContour();
140     length = meas.getLength();
141     REPORTER_ASSERT(reporter, length == SK_Scalar1);
142     SkPoint position;
143     SkVector tangent;
144     REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
145     REPORTER_ASSERT(reporter,
146         SkScalarNearlyEqual(position.fX,
147                             -SK_ScalarHalf,
148                             0.0001f));
149     REPORTER_ASSERT(reporter, position.fY == 0);
150     REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1);
151     REPORTER_ASSERT(reporter, tangent.fY == 0);
152 
153     // Test degenerate paths
154     path.reset();
155     path.moveTo(0, 0);
156     path.lineTo(0, 0);
157     path.lineTo(SK_Scalar1, 0);
158     path.quadTo(SK_Scalar1, 0, SK_Scalar1, 0);
159     path.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1 * 2);
160     path.cubicTo(SK_Scalar1, SK_Scalar1 * 2,
161                  SK_Scalar1, SK_Scalar1 * 2,
162                  SK_Scalar1, SK_Scalar1 * 2);
163     path.cubicTo(SK_Scalar1*2, SK_Scalar1 * 2,
164                  SK_Scalar1*3, SK_Scalar1 * 2,
165                  SK_Scalar1*4, SK_Scalar1 * 2);
166     meas.setPath(&path, false);
167     length = meas.getLength();
168     REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6);
169     REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
170     REPORTER_ASSERT(reporter,
171         SkScalarNearlyEqual(position.fX,
172                             SK_ScalarHalf,
173                             0.0001f));
174     REPORTER_ASSERT(reporter, position.fY == 0);
175     REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
176     REPORTER_ASSERT(reporter, tangent.fY == 0);
177     REPORTER_ASSERT(reporter, meas.getPosTan(2.5f, &position, &tangent));
178     REPORTER_ASSERT(reporter,
179         SkScalarNearlyEqual(position.fX, SK_Scalar1, 0.0001f));
180     REPORTER_ASSERT(reporter,
181         SkScalarNearlyEqual(position.fY, 1.5f));
182     REPORTER_ASSERT(reporter, tangent.fX == 0);
183     REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1);
184     REPORTER_ASSERT(reporter, meas.getPosTan(4.5f, &position, &tangent));
185     REPORTER_ASSERT(reporter,
186         SkScalarNearlyEqual(position.fX,
187                             2.5f,
188                             0.0001f));
189     REPORTER_ASSERT(reporter,
190         SkScalarNearlyEqual(position.fY,
191                             2.0f,
192                             0.0001f));
193     REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
194     REPORTER_ASSERT(reporter, tangent.fY == 0);
195 
196     path.reset();
197     path.moveTo(0, 0);
198     path.lineTo(SK_Scalar1, 0);
199     path.moveTo(SK_Scalar1, SK_Scalar1);
200     path.moveTo(SK_Scalar1 * 2, SK_Scalar1 * 2);
201     path.lineTo(SK_Scalar1, SK_Scalar1 * 2);
202     meas.setPath(&path, false);
203     length = meas.getLength();
204     REPORTER_ASSERT(reporter, length == SK_Scalar1);
205     REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
206     REPORTER_ASSERT(reporter,
207         SkScalarNearlyEqual(position.fX,
208                             SK_ScalarHalf,
209                             0.0001f));
210     REPORTER_ASSERT(reporter, position.fY == 0);
211     REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
212     REPORTER_ASSERT(reporter, tangent.fY == 0);
213     meas.nextContour();
214     length = meas.getLength();
215     REPORTER_ASSERT(reporter, length == SK_Scalar1);
216     REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
217     REPORTER_ASSERT(reporter,
218         SkScalarNearlyEqual(position.fX,
219                             1.5f,
220                             0.0001f));
221     REPORTER_ASSERT(reporter,
222         SkScalarNearlyEqual(position.fY,
223                             2.0f,
224                             0.0001f));
225     REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1);
226     REPORTER_ASSERT(reporter, tangent.fY == 0);
227 
228     test_small_segment();
229     test_small_segment2();
230     test_small_segment3(reporter);
231 
232     // SkPathMeasure isn't copyable, but it should be move-able
233     SkPathMeasure meas2(std::move(meas));
234     meas = std::move(meas2);
235 }
236 
DEF_TEST(PathMeasureConic,reporter)237 DEF_TEST(PathMeasureConic, reporter) {
238     SkPoint stdP, hiP, pts[] = {{0,0}, {100,0}, {100,0}};
239     SkPath p;
240     p.moveTo(0, 0);
241     p.conicTo(pts[1], pts[2], 1);
242     SkPathMeasure stdm(p, false);
243     REPORTER_ASSERT(reporter, stdm.getPosTan(20, &stdP, nullptr));
244     p.reset();
245     p.moveTo(0, 0);
246     p.conicTo(pts[1], pts[2], 10);
247     stdm.setPath(&p, false);
248     REPORTER_ASSERT(reporter, stdm.getPosTan(20, &hiP, nullptr));
249     REPORTER_ASSERT(reporter, 19.5f < stdP.fX && stdP.fX < 20.5f);
250     REPORTER_ASSERT(reporter, 19.5f < hiP.fX && hiP.fX < 20.5f);
251 }
252 
253 // Regression test for b/26425223
DEF_TEST(PathMeasure_nextctr,reporter)254 DEF_TEST(PathMeasure_nextctr, reporter) {
255     SkPath path;
256     path.moveTo(0, 0); path.lineTo(100, 0);
257 
258     SkPathMeasure meas(path, false);
259     // only expect 1 contour, even if we didn't explicitly call getLength() ourselves
260     REPORTER_ASSERT(reporter, !meas.nextContour());
261 }
262 
test_90_degrees(const sk_sp<SkContourMeasure> & cm,SkScalar radius,skiatest::Reporter * reporter)263 static void test_90_degrees(const sk_sp<SkContourMeasure>& cm, SkScalar radius,
264                             skiatest::Reporter* reporter) {
265     SkPoint pos;
266     SkVector tan;
267     SkScalar distance = cm->length() / 4;
268     bool success = cm->getPosTan(distance, &pos, &tan);
269 
270     REPORTER_ASSERT(reporter, success);
271     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fX, 0));
272     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fY, radius));
273     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fX, -1));
274     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fY, 0));
275 }
276 
test_empty_contours(skiatest::Reporter * reporter)277 static void test_empty_contours(skiatest::Reporter* reporter) {
278     SkPath path;
279 
280     path.moveTo(0, 0).lineTo(100, 100).lineTo(200, 100);
281     path.moveTo(2, 2).moveTo(3, 3);                 // zero-length(s)
282     path.moveTo(4, 4).close().close().close();      // zero-length
283     path.moveTo(5, 5).lineTo(5, 5);                 // zero-length
284     path.moveTo(5, 5).lineTo(5, 5).close();         // zero-length
285     path.moveTo(5, 5).lineTo(5, 5).close().close(); // zero-length
286     path.moveTo(6, 6).lineTo(7, 7);
287     path.moveTo(10, 10);                            // zero-length
288 
289     SkContourMeasureIter fact(path, false);
290 
291     // given the above construction, we expect only 2 contours (the rest are "empty")
292 
293     REPORTER_ASSERT(reporter, fact.next());
294     REPORTER_ASSERT(reporter, fact.next());
295     REPORTER_ASSERT(reporter, !fact.next());
296 }
297 
test_MLM_contours(skiatest::Reporter * reporter)298 static void test_MLM_contours(skiatest::Reporter* reporter) {
299     SkPath path;
300 
301     // This odd sequence (with a trailing moveTo) used to return a 2nd contour, which is
302     // wrong, since the contract for a measure is to only return non-zero length contours.
303     path.moveTo(10, 10).lineTo(20, 20).moveTo(30, 30);
304 
305     for (bool forceClosed : {false, true}) {
306         SkContourMeasureIter fact(path, forceClosed);
307         REPORTER_ASSERT(reporter, fact.next());
308         REPORTER_ASSERT(reporter, !fact.next());
309     }
310 }
311 
test_shrink(skiatest::Reporter * reporter)312 static void test_shrink(skiatest::Reporter* reporter) {
313     SkPath path;
314     path.addRect({1, 2, 3, 4});
315     path.incReserve(100);   // give shrinkToFit() something to do
316 
317     SkContourMeasureIter iter(path, false);
318 
319     // shrinks the allocation, possibly relocating the underlying arrays.
320     // The contouremasureiter needs to have safely copied path, to be unaffected by this
321     // change to "path".
322     SkPathPriv::ShrinkToFit(&path);
323 
324     // Note, this failed (before the fix) on an ASAN build, which notices that we were
325     // using an internal iterator of the passed-in path, not our copy.
326     while (iter.next())
327         ;
328 }
329 
DEF_TEST(contour_measure,reporter)330 DEF_TEST(contour_measure, reporter) {
331     SkPath path;
332     path.addCircle(0, 0, 100);
333     path.addCircle(0, 0, 10);
334 
335     SkContourMeasureIter fact(path, false);
336     path.reset();   // we should not need the path avert we created the factory
337 
338     auto cm0 = fact.next();
339     auto cm1 = fact.next();
340 
341     REPORTER_ASSERT(reporter, cm0->isClosed());
342     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm0->length(), 200 * SK_ScalarPI, 1.5f));
343 
344     test_90_degrees(cm0, 100, reporter);
345 
346     REPORTER_ASSERT(reporter, cm1->isClosed());
347     REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm1->length(), 20 * SK_ScalarPI, 0.5f));
348 
349     test_90_degrees(cm1, 10, reporter);
350 
351     auto cm2 = fact.next();
352     REPORTER_ASSERT(reporter, !cm2);
353 
354     test_empty_contours(reporter);
355     test_MLM_contours(reporter);
356 
357     test_shrink(reporter);
358 }
359