blob: caff6df399d5318aedda719d2a744cad3a5bce42 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
reed@android.com8a1c16f2008-12-17 15:59:43 +00002/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00003 * Copyright 2008 The Android Open Source Project
reed@android.com8a1c16f2008-12-17 15:59:43 +00004 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00005 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
reed@android.com8a1c16f2008-12-17 15:59:43 +00007 */
8
epoger@google.comec3ed6a2011-07-28 14:26:00 +00009
reed@android.com8a1c16f2008-12-17 15:59:43 +000010#include "SkPathMeasure.h"
11#include "SkGeometry.h"
12#include "SkPath.h"
13#include "SkTSearch.h"
14
reed220f9262014-12-17 08:21:04 -080015// these must be 0,1,2,3 since they are in our 2-bit field
reed@android.com8a1c16f2008-12-17 15:59:43 +000016enum {
17 kLine_SegType,
reed@android.com8a1c16f2008-12-17 15:59:43 +000018 kQuad_SegType,
reed220f9262014-12-17 08:21:04 -080019 kCubic_SegType,
20 kConic_SegType,
reed@android.com8a1c16f2008-12-17 15:59:43 +000021};
22
23#define kMaxTValue 32767
24
25static inline SkScalar tValue2Scalar(int t) {
26 SkASSERT((unsigned)t <= kMaxTValue);
reed@android.com8a1c16f2008-12-17 15:59:43 +000027 return t * 3.05185e-5f; // t / 32767
reed@android.com8a1c16f2008-12-17 15:59:43 +000028}
29
30SkScalar SkPathMeasure::Segment::getScalarT() const {
31 return tValue2Scalar(fTValue);
32}
33
34const SkPathMeasure::Segment* SkPathMeasure::NextSegment(const Segment* seg) {
35 unsigned ptIndex = seg->fPtIndex;
36
37 do {
38 ++seg;
39 } while (seg->fPtIndex == ptIndex);
40 return seg;
41}
42
43///////////////////////////////////////////////////////////////////////////////
44
45static inline int tspan_big_enough(int tspan) {
46 SkASSERT((unsigned)tspan <= kMaxTValue);
47 return tspan >> 10;
48}
49
reed@android.com8a1c16f2008-12-17 15:59:43 +000050// can't use tangents, since we need [0..1..................2] to be seen
51// as definitely not a line (it is when drawn, but not parametrically)
52// so we compare midpoints
53#define CHEAP_DIST_LIMIT (SK_Scalar1/2) // just made this value up
54
55static bool quad_too_curvy(const SkPoint pts[3]) {
56 // diff = (a/4 + b/2 + c/4) - (a/2 + c/2)
57 // diff = -a/4 + b/2 - c/4
58 SkScalar dx = SkScalarHalf(pts[1].fX) -
59 SkScalarHalf(SkScalarHalf(pts[0].fX + pts[2].fX));
60 SkScalar dy = SkScalarHalf(pts[1].fY) -
61 SkScalarHalf(SkScalarHalf(pts[0].fY + pts[2].fY));
62
63 SkScalar dist = SkMaxScalar(SkScalarAbs(dx), SkScalarAbs(dy));
64 return dist > CHEAP_DIST_LIMIT;
65}
66
67static bool cheap_dist_exceeds_limit(const SkPoint& pt,
68 SkScalar x, SkScalar y) {
69 SkScalar dist = SkMaxScalar(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY));
70 // just made up the 1/2
71 return dist > CHEAP_DIST_LIMIT;
72}
73
74static bool cubic_too_curvy(const SkPoint pts[4]) {
75 return cheap_dist_exceeds_limit(pts[1],
76 SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1/3),
77 SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1/3))
78 ||
79 cheap_dist_exceeds_limit(pts[2],
80 SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1*2/3),
81 SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3));
82}
83
84SkScalar SkPathMeasure::compute_quad_segs(const SkPoint pts[3],
85 SkScalar distance, int mint, int maxt, int ptIndex) {
86 if (tspan_big_enough(maxt - mint) && quad_too_curvy(pts)) {
87 SkPoint tmp[5];
88 int halft = (mint + maxt) >> 1;
89
90 SkChopQuadAtHalf(pts, tmp);
91 distance = this->compute_quad_segs(tmp, distance, mint, halft, ptIndex);
92 distance = this->compute_quad_segs(&tmp[2], distance, halft, maxt, ptIndex);
93 } else {
94 SkScalar d = SkPoint::Distance(pts[0], pts[2]);
reed@google.comded44142012-04-27 20:22:07 +000095 SkScalar prevD = distance;
96 distance += d;
97 if (distance > prevD) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000098 Segment* seg = fSegments.append();
99 seg->fDistance = distance;
100 seg->fPtIndex = ptIndex;
101 seg->fType = kQuad_SegType;
102 seg->fTValue = maxt;
103 }
104 }
105 return distance;
106}
107
reed220f9262014-12-17 08:21:04 -0800108SkScalar SkPathMeasure::compute_conic_segs(const SkConic& conic,
109 SkScalar distance, int mint, int maxt, int ptIndex) {
110 if (tspan_big_enough(maxt - mint) && quad_too_curvy(conic.fPts)) {
111 SkConic tmp[2];
112 conic.chop(tmp);
113
114 int halft = (mint + maxt) >> 1;
115 distance = this->compute_conic_segs(tmp[0], distance, mint, halft, ptIndex);
116 distance = this->compute_conic_segs(tmp[1], distance, halft, maxt, ptIndex);
117 } else {
118 SkScalar d = SkPoint::Distance(conic.fPts[0], conic.fPts[2]);
119 SkScalar prevD = distance;
120 distance += d;
121 if (distance > prevD) {
122 Segment* seg = fSegments.append();
123 seg->fDistance = distance;
124 seg->fPtIndex = ptIndex;
125 seg->fType = kConic_SegType;
126 seg->fTValue = maxt;
127 }
128 }
129 return distance;
130}
131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132SkScalar SkPathMeasure::compute_cubic_segs(const SkPoint pts[4],
133 SkScalar distance, int mint, int maxt, int ptIndex) {
134 if (tspan_big_enough(maxt - mint) && cubic_too_curvy(pts)) {
135 SkPoint tmp[7];
136 int halft = (mint + maxt) >> 1;
137
138 SkChopCubicAtHalf(pts, tmp);
139 distance = this->compute_cubic_segs(tmp, distance, mint, halft, ptIndex);
140 distance = this->compute_cubic_segs(&tmp[3], distance, halft, maxt, ptIndex);
141 } else {
142 SkScalar d = SkPoint::Distance(pts[0], pts[3]);
reed@google.comded44142012-04-27 20:22:07 +0000143 SkScalar prevD = distance;
144 distance += d;
145 if (distance > prevD) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000146 Segment* seg = fSegments.append();
147 seg->fDistance = distance;
148 seg->fPtIndex = ptIndex;
149 seg->fType = kCubic_SegType;
150 seg->fTValue = maxt;
151 }
152 }
153 return distance;
154}
155
156void SkPathMeasure::buildSegments() {
157 SkPoint pts[4];
158 int ptIndex = fFirstPtIndex;
reed@google.comfab1ddd2012-04-20 15:10:32 +0000159 SkScalar distance = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 bool isClosed = fForceClosed;
161 bool firstMoveTo = ptIndex < 0;
162 Segment* seg;
163
reed@google.comfab1ddd2012-04-20 15:10:32 +0000164 /* Note:
165 * as we accumulate distance, we have to check that the result of +=
166 * actually made it larger, since a very small delta might be > 0, but
167 * still have no effect on distance (if distance >>> delta).
reed@google.comded44142012-04-27 20:22:07 +0000168 *
169 * We do this check below, and in compute_quad_segs and compute_cubic_segs
reed@google.comfab1ddd2012-04-20 15:10:32 +0000170 */
reed@android.com8a1c16f2008-12-17 15:59:43 +0000171 fSegments.reset();
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000172 bool done = false;
173 do {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 switch (fIter.next(pts)) {
175 case SkPath::kMove_Verb:
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000176 ptIndex += 1;
177 fPts.append(1, pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 if (!firstMoveTo) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000179 done = true;
180 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181 }
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000182 firstMoveTo = false;
183 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184
reed@google.comfab1ddd2012-04-20 15:10:32 +0000185 case SkPath::kLine_Verb: {
186 SkScalar d = SkPoint::Distance(pts[0], pts[1]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187 SkASSERT(d >= 0);
reed@google.comfab1ddd2012-04-20 15:10:32 +0000188 SkScalar prevD = distance;
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000189 distance += d;
reed@google.comfab1ddd2012-04-20 15:10:32 +0000190 if (distance > prevD) {
191 seg = fSegments.append();
192 seg->fDistance = distance;
193 seg->fPtIndex = ptIndex;
194 seg->fType = kLine_SegType;
195 seg->fTValue = kMaxTValue;
196 fPts.append(1, pts + 1);
197 ptIndex++;
198 }
199 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200
reed@google.comfab1ddd2012-04-20 15:10:32 +0000201 case SkPath::kQuad_Verb: {
202 SkScalar prevD = distance;
reed220f9262014-12-17 08:21:04 -0800203 distance = this->compute_quad_segs(pts, distance, 0, kMaxTValue, ptIndex);
reed@google.comfab1ddd2012-04-20 15:10:32 +0000204 if (distance > prevD) {
205 fPts.append(2, pts + 1);
206 ptIndex += 2;
207 }
208 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209
reed220f9262014-12-17 08:21:04 -0800210 case SkPath::kConic_Verb: {
211 const SkConic conic(pts, fIter.conicWeight());
212 SkScalar prevD = distance;
213 distance = this->compute_conic_segs(conic, distance, 0, kMaxTValue, ptIndex);
214 if (distance > prevD) {
215 // we store the conic weight in our next point, followed by the last 2 pts
216 // thus to reconstitue a conic, you'd need to say
217 // SkConic(pts[0], pts[2], pts[3], weight = pts[1].fX)
218 fPts.append()->set(conic.fW, 0);
219 fPts.append(2, pts + 1);
220 ptIndex += 3;
221 }
222 } break;
223
reed@google.comfab1ddd2012-04-20 15:10:32 +0000224 case SkPath::kCubic_Verb: {
225 SkScalar prevD = distance;
reed220f9262014-12-17 08:21:04 -0800226 distance = this->compute_cubic_segs(pts, distance, 0, kMaxTValue, ptIndex);
reed@google.comfab1ddd2012-04-20 15:10:32 +0000227 if (distance > prevD) {
228 fPts.append(3, pts + 1);
229 ptIndex += 3;
230 }
231 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232
233 case SkPath::kClose_Verb:
234 isClosed = true;
235 break;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000236
reed@android.com8a1c16f2008-12-17 15:59:43 +0000237 case SkPath::kDone_Verb:
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000238 done = true;
239 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 }
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000241 } while (!done);
242
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 fLength = distance;
244 fIsClosed = isClosed;
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000245 fFirstPtIndex = ptIndex;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246
247#ifdef SK_DEBUG
248 {
249 const Segment* seg = fSegments.begin();
250 const Segment* stop = fSegments.end();
251 unsigned ptIndex = 0;
252 SkScalar distance = 0;
253
254 while (seg < stop) {
255 SkASSERT(seg->fDistance > distance);
256 SkASSERT(seg->fPtIndex >= ptIndex);
257 SkASSERT(seg->fTValue > 0);
258
259 const Segment* s = seg;
260 while (s < stop - 1 && s[0].fPtIndex == s[1].fPtIndex) {
261 SkASSERT(s[0].fType == s[1].fType);
262 SkASSERT(s[0].fTValue < s[1].fTValue);
263 s += 1;
264 }
265
266 distance = seg->fDistance;
267 ptIndex = seg->fPtIndex;
268 seg += 1;
269 }
270 // SkDebugf("\n");
271 }
272#endif
273}
274
reed@google.com5b941532012-05-17 15:31:43 +0000275static void compute_pos_tan(const SkPoint pts[], int segType,
276 SkScalar t, SkPoint* pos, SkVector* tangent) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 switch (segType) {
278 case kLine_SegType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 if (pos) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000280 pos->set(SkScalarInterp(pts[0].fX, pts[1].fX, t),
reed@google.com5b941532012-05-17 15:31:43 +0000281 SkScalarInterp(pts[0].fY, pts[1].fY, t));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 }
283 if (tangent) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000284 tangent->setNormalize(pts[1].fX - pts[0].fX, pts[1].fY - pts[0].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000285 }
286 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000287 case kQuad_SegType:
288 SkEvalQuadAt(pts, t, pos, tangent);
289 if (tangent) {
290 tangent->normalize();
291 }
292 break;
reed220f9262014-12-17 08:21:04 -0800293 case kConic_SegType: {
294 SkConic(pts[0], pts[2], pts[3], pts[1].fX).evalAt(t, pos, tangent);
295 if (tangent) {
296 tangent->normalize();
297 }
298 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 case kCubic_SegType:
halcanary96fcdcc2015-08-27 07:41:13 -0700300 SkEvalCubicAt(pts, t, pos, tangent, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 if (tangent) {
302 tangent->normalize();
303 }
304 break;
305 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000306 SkDEBUGFAIL("unknown segType");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307 }
308}
309
reed@google.com5b941532012-05-17 15:31:43 +0000310static void seg_to(const SkPoint pts[], int segType,
311 SkScalar startT, SkScalar stopT, SkPath* dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 SkASSERT(startT >= 0 && startT <= SK_Scalar1);
313 SkASSERT(stopT >= 0 && stopT <= SK_Scalar1);
314 SkASSERT(startT <= stopT);
315
reed@google.comde1837b2012-05-21 16:47:43 +0000316 if (startT == stopT) {
caryclark5cb00a92015-08-26 09:04:55 -0700317 /* if the dash as a zero-length on segment, add a corresponding zero-length line.
318 The stroke code will add end caps to zero length lines as appropriate */
319 SkPoint lastPt;
320 SkAssertResult(dst->getLastPt(&lastPt));
321 dst->lineTo(lastPt);
322 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000323 }
324
reed220f9262014-12-17 08:21:04 -0800325 SkPoint tmp0[7], tmp1[7];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326
327 switch (segType) {
328 case kLine_SegType:
reed@google.comf33612b2013-02-20 14:55:20 +0000329 if (SK_Scalar1 == stopT) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000330 dst->lineTo(pts[1]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000331 } else {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000332 dst->lineTo(SkScalarInterp(pts[0].fX, pts[1].fX, stopT),
333 SkScalarInterp(pts[0].fY, pts[1].fY, stopT));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000334 }
335 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000336 case kQuad_SegType:
reed@google.comf33612b2013-02-20 14:55:20 +0000337 if (0 == startT) {
338 if (SK_Scalar1 == stopT) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000339 dst->quadTo(pts[1], pts[2]);
340 } else {
341 SkChopQuadAt(pts, tmp0, stopT);
342 dst->quadTo(tmp0[1], tmp0[2]);
343 }
344 } else {
345 SkChopQuadAt(pts, tmp0, startT);
reed@google.comf33612b2013-02-20 14:55:20 +0000346 if (SK_Scalar1 == stopT) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 dst->quadTo(tmp0[3], tmp0[4]);
348 } else {
reed80ea19c2015-05-12 10:37:34 -0700349 SkChopQuadAt(&tmp0[2], tmp1, (stopT - startT) / (1 - startT));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350 dst->quadTo(tmp1[1], tmp1[2]);
351 }
352 }
353 break;
reed220f9262014-12-17 08:21:04 -0800354 case kConic_SegType: {
355 SkConic conic(pts[0], pts[2], pts[3], pts[1].fX);
356
357 if (0 == startT) {
358 if (SK_Scalar1 == stopT) {
359 dst->conicTo(conic.fPts[1], conic.fPts[2], conic.fW);
360 } else {
361 SkConic tmp[2];
362 conic.chopAt(stopT, tmp);
363 dst->conicTo(tmp[0].fPts[1], tmp[0].fPts[2], tmp[0].fW);
364 }
365 } else {
366 SkConic tmp1[2];
367 conic.chopAt(startT, tmp1);
368 if (SK_Scalar1 == stopT) {
369 dst->conicTo(tmp1[1].fPts[1], tmp1[1].fPts[2], tmp1[1].fW);
370 } else {
371 SkConic tmp2[2];
372 tmp1[1].chopAt((stopT - startT) / (SK_Scalar1 - startT), tmp2);
373 dst->conicTo(tmp2[0].fPts[1], tmp2[0].fPts[2], tmp2[0].fW);
374 }
375 }
376 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000377 case kCubic_SegType:
reed@google.comf33612b2013-02-20 14:55:20 +0000378 if (0 == startT) {
379 if (SK_Scalar1 == stopT) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380 dst->cubicTo(pts[1], pts[2], pts[3]);
381 } else {
382 SkChopCubicAt(pts, tmp0, stopT);
383 dst->cubicTo(tmp0[1], tmp0[2], tmp0[3]);
384 }
385 } else {
386 SkChopCubicAt(pts, tmp0, startT);
reed@google.comf33612b2013-02-20 14:55:20 +0000387 if (SK_Scalar1 == stopT) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388 dst->cubicTo(tmp0[4], tmp0[5], tmp0[6]);
389 } else {
reed80ea19c2015-05-12 10:37:34 -0700390 SkChopCubicAt(&tmp0[3], tmp1, (stopT - startT) / (1 - startT));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 dst->cubicTo(tmp1[1], tmp1[2], tmp1[3]);
392 }
393 }
394 break;
395 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000396 SkDEBUGFAIL("unknown segType");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397 sk_throw();
398 }
399}
400
401////////////////////////////////////////////////////////////////////////////////
402////////////////////////////////////////////////////////////////////////////////
403
404SkPathMeasure::SkPathMeasure() {
halcanary96fcdcc2015-08-27 07:41:13 -0700405 fPath = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 fLength = -1; // signal we need to compute it
407 fForceClosed = false;
408 fFirstPtIndex = -1;
409}
410
411SkPathMeasure::SkPathMeasure(const SkPath& path, bool forceClosed) {
412 fPath = &path;
413 fLength = -1; // signal we need to compute it
414 fForceClosed = forceClosed;
415 fFirstPtIndex = -1;
416
417 fIter.setPath(path, forceClosed);
418}
419
420SkPathMeasure::~SkPathMeasure() {}
421
422/** Assign a new path, or null to have none.
423*/
424void SkPathMeasure::setPath(const SkPath* path, bool forceClosed) {
425 fPath = path;
426 fLength = -1; // signal we need to compute it
427 fForceClosed = forceClosed;
428 fFirstPtIndex = -1;
429
430 if (path) {
431 fIter.setPath(*path, forceClosed);
432 }
433 fSegments.reset();
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000434 fPts.reset();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000435}
436
437SkScalar SkPathMeasure::getLength() {
halcanary96fcdcc2015-08-27 07:41:13 -0700438 if (fPath == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439 return 0;
440 }
441 if (fLength < 0) {
442 this->buildSegments();
443 }
444 SkASSERT(fLength >= 0);
445 return fLength;
446}
447
reed83e22e42015-03-26 04:13:08 -0700448template <typename T, typename K>
449int SkTKSearch(const T base[], int count, const K& key) {
450 SkASSERT(count >= 0);
451 if (count <= 0) {
452 return ~0;
453 }
454
halcanary96fcdcc2015-08-27 07:41:13 -0700455 SkASSERT(base != nullptr); // base may be nullptr if count is zero
reed83e22e42015-03-26 04:13:08 -0700456
457 int lo = 0;
458 int hi = count - 1;
459
460 while (lo < hi) {
461 int mid = (hi + lo) >> 1;
462 if (base[mid].fDistance < key) {
463 lo = mid + 1;
464 } else {
465 hi = mid;
466 }
467 }
468
469 if (base[hi].fDistance < key) {
470 hi += 1;
471 hi = ~hi;
472 } else if (key < base[hi].fDistance) {
473 hi = ~hi;
474 }
475 return hi;
476}
477
reed@android.com8a1c16f2008-12-17 15:59:43 +0000478const SkPathMeasure::Segment* SkPathMeasure::distanceToSegment(
479 SkScalar distance, SkScalar* t) {
480 SkDEBUGCODE(SkScalar length = ) this->getLength();
481 SkASSERT(distance >= 0 && distance <= length);
482
483 const Segment* seg = fSegments.begin();
484 int count = fSegments.count();
485
reed83e22e42015-03-26 04:13:08 -0700486 int index = SkTKSearch<Segment, SkScalar>(seg, count, distance);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000487 // don't care if we hit an exact match or not, so we xor index if it is negative
488 index ^= (index >> 31);
489 seg = &seg[index];
490
491 // now interpolate t-values with the prev segment (if possible)
492 SkScalar startT = 0, startD = 0;
493 // check if the prev segment is legal, and references the same set of points
494 if (index > 0) {
495 startD = seg[-1].fDistance;
496 if (seg[-1].fPtIndex == seg->fPtIndex) {
497 SkASSERT(seg[-1].fType == seg->fType);
498 startT = seg[-1].getScalarT();
499 }
500 }
501
502 SkASSERT(seg->getScalarT() > startT);
503 SkASSERT(distance >= startD);
504 SkASSERT(seg->fDistance > startD);
505
506 *t = startT + SkScalarMulDiv(seg->getScalarT() - startT,
507 distance - startD,
508 seg->fDistance - startD);
509 return seg;
510}
511
512bool SkPathMeasure::getPosTan(SkScalar distance, SkPoint* pos,
513 SkVector* tangent) {
halcanary96fcdcc2015-08-27 07:41:13 -0700514 if (nullptr == fPath) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000515 return false;
516 }
517
518 SkScalar length = this->getLength(); // call this to force computing it
519 int count = fSegments.count();
520
521 if (count == 0 || length == 0) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000522 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000523 }
524
525 // pin the distance to a legal range
526 if (distance < 0) {
527 distance = 0;
528 } else if (distance > length) {
529 distance = length;
530 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000531
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532 SkScalar t;
533 const Segment* seg = this->distanceToSegment(distance, &t);
534
reed@google.com5b941532012-05-17 15:31:43 +0000535 compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000536 return true;
537}
538
539bool SkPathMeasure::getMatrix(SkScalar distance, SkMatrix* matrix,
540 MatrixFlags flags) {
halcanary96fcdcc2015-08-27 07:41:13 -0700541 if (nullptr == fPath) {
djsollen@google.comdfef4562012-11-01 12:15:12 +0000542 return false;
543 }
544
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545 SkPoint position;
546 SkVector tangent;
547
548 if (this->getPosTan(distance, &position, &tangent)) {
549 if (matrix) {
550 if (flags & kGetTangent_MatrixFlag) {
551 matrix->setSinCos(tangent.fY, tangent.fX, 0, 0);
552 } else {
553 matrix->reset();
554 }
555 if (flags & kGetPosition_MatrixFlag) {
556 matrix->postTranslate(position.fX, position.fY);
557 }
558 }
559 return true;
560 }
561 return false;
562}
563
564bool SkPathMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst,
565 bool startWithMoveTo) {
566 SkASSERT(dst);
567
568 SkScalar length = this->getLength(); // ensure we have built our segments
569
570 if (startD < 0) {
571 startD = 0;
572 }
573 if (stopD > length) {
574 stopD = length;
575 }
caryclark5cb00a92015-08-26 09:04:55 -0700576 if (startD > stopD) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577 return false;
578 }
579
580 SkPoint p;
581 SkScalar startT, stopT;
582 const Segment* seg = this->distanceToSegment(startD, &startT);
583 const Segment* stopSeg = this->distanceToSegment(stopD, &stopT);
584 SkASSERT(seg <= stopSeg);
585
586 if (startWithMoveTo) {
halcanary96fcdcc2015-08-27 07:41:13 -0700587 compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, startT, &p, nullptr);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588 dst->moveTo(p);
589 }
590
591 if (seg->fPtIndex == stopSeg->fPtIndex) {
reed@google.com5b941532012-05-17 15:31:43 +0000592 seg_to(&fPts[seg->fPtIndex], seg->fType, startT, stopT, dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593 } else {
594 do {
reed@google.com5b941532012-05-17 15:31:43 +0000595 seg_to(&fPts[seg->fPtIndex], seg->fType, startT, SK_Scalar1, dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000596 seg = SkPathMeasure::NextSegment(seg);
597 startT = 0;
598 } while (seg->fPtIndex < stopSeg->fPtIndex);
reed@google.com5b941532012-05-17 15:31:43 +0000599 seg_to(&fPts[seg->fPtIndex], seg->fType, 0, stopT, dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000600 }
601 return true;
602}
603
604bool SkPathMeasure::isClosed() {
605 (void)this->getLength();
606 return fIsClosed;
607}
608
609/** Move to the next contour in the path. Return true if one exists, or false if
610 we're done with the path.
611*/
612bool SkPathMeasure::nextContour() {
613 fLength = -1;
614 return this->getLength() > 0;
615}
616
617///////////////////////////////////////////////////////////////////////////////
618///////////////////////////////////////////////////////////////////////////////
619
620#ifdef SK_DEBUG
621
622void SkPathMeasure::dump() {
623 SkDebugf("pathmeas: length=%g, segs=%d\n", fLength, fSegments.count());
624
625 for (int i = 0; i < fSegments.count(); i++) {
626 const Segment* seg = &fSegments[i];
627 SkDebugf("pathmeas: seg[%d] distance=%g, point=%d, t=%g, type=%d\n",
628 i, seg->fDistance, seg->fPtIndex, seg->getScalarT(),
629 seg->fType);
630 }
631}
632
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633#endif