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