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