blob: 887c0c18fbb6093141410e2a1c574676081daf85 [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
15// these must be 0,1,2 since they are in our 2-bit field
16enum {
17 kLine_SegType,
reed@android.com8a1c16f2008-12-17 15:59:43 +000018 kQuad_SegType,
19 kCubic_SegType
20};
21
22#define kMaxTValue 32767
23
24static inline SkScalar tValue2Scalar(int t) {
25 SkASSERT((unsigned)t <= kMaxTValue);
26
27#ifdef SK_SCALAR_IS_FLOAT
28 return t * 3.05185e-5f; // t / 32767
29#else
30 return (t + (t >> 14)) << 1;
31#endif
32}
33
34SkScalar SkPathMeasure::Segment::getScalarT() const {
35 return tValue2Scalar(fTValue);
36}
37
38const SkPathMeasure::Segment* SkPathMeasure::NextSegment(const Segment* seg) {
39 unsigned ptIndex = seg->fPtIndex;
40
41 do {
42 ++seg;
43 } while (seg->fPtIndex == ptIndex);
44 return seg;
45}
46
47///////////////////////////////////////////////////////////////////////////////
48
49static inline int tspan_big_enough(int tspan) {
50 SkASSERT((unsigned)tspan <= kMaxTValue);
51 return tspan >> 10;
52}
53
reed@android.com8a1c16f2008-12-17 15:59:43 +000054// can't use tangents, since we need [0..1..................2] to be seen
55// as definitely not a line (it is when drawn, but not parametrically)
56// so we compare midpoints
57#define CHEAP_DIST_LIMIT (SK_Scalar1/2) // just made this value up
58
59static bool quad_too_curvy(const SkPoint pts[3]) {
60 // diff = (a/4 + b/2 + c/4) - (a/2 + c/2)
61 // diff = -a/4 + b/2 - c/4
62 SkScalar dx = SkScalarHalf(pts[1].fX) -
63 SkScalarHalf(SkScalarHalf(pts[0].fX + pts[2].fX));
64 SkScalar dy = SkScalarHalf(pts[1].fY) -
65 SkScalarHalf(SkScalarHalf(pts[0].fY + pts[2].fY));
66
67 SkScalar dist = SkMaxScalar(SkScalarAbs(dx), SkScalarAbs(dy));
68 return dist > CHEAP_DIST_LIMIT;
69}
70
71static bool cheap_dist_exceeds_limit(const SkPoint& pt,
72 SkScalar x, SkScalar y) {
73 SkScalar dist = SkMaxScalar(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY));
74 // just made up the 1/2
75 return dist > CHEAP_DIST_LIMIT;
76}
77
78static bool cubic_too_curvy(const SkPoint pts[4]) {
79 return cheap_dist_exceeds_limit(pts[1],
80 SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1/3),
81 SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1/3))
82 ||
83 cheap_dist_exceeds_limit(pts[2],
84 SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1*2/3),
85 SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3));
86}
87
88SkScalar SkPathMeasure::compute_quad_segs(const SkPoint pts[3],
89 SkScalar distance, int mint, int maxt, int ptIndex) {
90 if (tspan_big_enough(maxt - mint) && quad_too_curvy(pts)) {
91 SkPoint tmp[5];
92 int halft = (mint + maxt) >> 1;
93
94 SkChopQuadAtHalf(pts, tmp);
95 distance = this->compute_quad_segs(tmp, distance, mint, halft, ptIndex);
96 distance = this->compute_quad_segs(&tmp[2], distance, halft, maxt, ptIndex);
97 } else {
98 SkScalar d = SkPoint::Distance(pts[0], pts[2]);
99 SkASSERT(d >= 0);
reed@google.comded44142012-04-27 20:22:07 +0000100 if (SkScalarNearlyZero(d)) {
101 d = 0;
102 }
103 SkScalar prevD = distance;
104 distance += d;
105 if (distance > prevD) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 Segment* seg = fSegments.append();
107 seg->fDistance = distance;
108 seg->fPtIndex = ptIndex;
109 seg->fType = kQuad_SegType;
110 seg->fTValue = maxt;
111 }
112 }
113 return distance;
114}
115
116SkScalar SkPathMeasure::compute_cubic_segs(const SkPoint pts[4],
117 SkScalar distance, int mint, int maxt, int ptIndex) {
118 if (tspan_big_enough(maxt - mint) && cubic_too_curvy(pts)) {
119 SkPoint tmp[7];
120 int halft = (mint + maxt) >> 1;
121
122 SkChopCubicAtHalf(pts, tmp);
123 distance = this->compute_cubic_segs(tmp, distance, mint, halft, ptIndex);
124 distance = this->compute_cubic_segs(&tmp[3], distance, halft, maxt, ptIndex);
125 } else {
126 SkScalar d = SkPoint::Distance(pts[0], pts[3]);
127 SkASSERT(d >= 0);
reed@google.comded44142012-04-27 20:22:07 +0000128 if (SkScalarNearlyZero(d)) {
129 d = 0;
130 }
131 SkScalar prevD = distance;
132 distance += d;
133 if (distance > prevD) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 Segment* seg = fSegments.append();
135 seg->fDistance = distance;
136 seg->fPtIndex = ptIndex;
137 seg->fType = kCubic_SegType;
138 seg->fTValue = maxt;
139 }
140 }
141 return distance;
142}
143
144void SkPathMeasure::buildSegments() {
145 SkPoint pts[4];
146 int ptIndex = fFirstPtIndex;
reed@google.comfab1ddd2012-04-20 15:10:32 +0000147 SkScalar distance = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 bool isClosed = fForceClosed;
149 bool firstMoveTo = ptIndex < 0;
150 Segment* seg;
151
reed@google.comfab1ddd2012-04-20 15:10:32 +0000152 /* Note:
153 * as we accumulate distance, we have to check that the result of +=
154 * actually made it larger, since a very small delta might be > 0, but
155 * still have no effect on distance (if distance >>> delta).
reed@google.comded44142012-04-27 20:22:07 +0000156 *
157 * We do this check below, and in compute_quad_segs and compute_cubic_segs
reed@google.comfab1ddd2012-04-20 15:10:32 +0000158 */
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 fSegments.reset();
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000160 bool done = false;
161 do {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 switch (fIter.next(pts)) {
163 case SkPath::kMove_Verb:
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000164 ptIndex += 1;
165 fPts.append(1, pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166 if (!firstMoveTo) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000167 done = true;
168 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 }
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000170 firstMoveTo = false;
171 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
reed@google.comfab1ddd2012-04-20 15:10:32 +0000173 case SkPath::kLine_Verb: {
174 SkScalar d = SkPoint::Distance(pts[0], pts[1]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 SkASSERT(d >= 0);
reed@google.comfab1ddd2012-04-20 15:10:32 +0000176 SkScalar prevD = distance;
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000177 distance += d;
reed@google.comfab1ddd2012-04-20 15:10:32 +0000178 if (distance > prevD) {
179 seg = fSegments.append();
180 seg->fDistance = distance;
181 seg->fPtIndex = ptIndex;
182 seg->fType = kLine_SegType;
183 seg->fTValue = kMaxTValue;
184 fPts.append(1, pts + 1);
185 ptIndex++;
186 }
187 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000188
reed@google.comfab1ddd2012-04-20 15:10:32 +0000189 case SkPath::kQuad_Verb: {
190 SkScalar prevD = distance;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 distance = this->compute_quad_segs(pts, distance, 0,
192 kMaxTValue, ptIndex);
reed@google.comfab1ddd2012-04-20 15:10:32 +0000193 if (distance > prevD) {
194 fPts.append(2, pts + 1);
195 ptIndex += 2;
196 }
197 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198
reed@google.comfab1ddd2012-04-20 15:10:32 +0000199 case SkPath::kCubic_Verb: {
200 SkScalar prevD = distance;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 distance = this->compute_cubic_segs(pts, distance, 0,
202 kMaxTValue, ptIndex);
reed@google.comfab1ddd2012-04-20 15:10:32 +0000203 if (distance > prevD) {
204 fPts.append(3, pts + 1);
205 ptIndex += 3;
206 }
207 } break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208
209 case SkPath::kClose_Verb:
210 isClosed = true;
211 break;
212
213 case SkPath::kDone_Verb:
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000214 done = true;
215 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216 }
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000217 } while (!done);
218
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 fLength = distance;
220 fIsClosed = isClosed;
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000221 fFirstPtIndex = ptIndex;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222
223#ifdef SK_DEBUG
224 {
225 const Segment* seg = fSegments.begin();
226 const Segment* stop = fSegments.end();
227 unsigned ptIndex = 0;
228 SkScalar distance = 0;
229
230 while (seg < stop) {
231 SkASSERT(seg->fDistance > distance);
232 SkASSERT(seg->fPtIndex >= ptIndex);
233 SkASSERT(seg->fTValue > 0);
234
235 const Segment* s = seg;
236 while (s < stop - 1 && s[0].fPtIndex == s[1].fPtIndex) {
237 SkASSERT(s[0].fType == s[1].fType);
238 SkASSERT(s[0].fTValue < s[1].fTValue);
239 s += 1;
240 }
241
242 distance = seg->fDistance;
243 ptIndex = seg->fPtIndex;
244 seg += 1;
245 }
246 // SkDebugf("\n");
247 }
248#endif
249}
250
reed@google.com5b941532012-05-17 15:31:43 +0000251static void compute_pos_tan(const SkPoint pts[], int segType,
252 SkScalar t, SkPoint* pos, SkVector* tangent) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000253 switch (segType) {
254 case kLine_SegType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255 if (pos) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000256 pos->set(SkScalarInterp(pts[0].fX, pts[1].fX, t),
reed@google.com5b941532012-05-17 15:31:43 +0000257 SkScalarInterp(pts[0].fY, pts[1].fY, t));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 }
259 if (tangent) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000260 tangent->setNormalize(pts[1].fX - pts[0].fX, pts[1].fY - pts[0].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000261 }
262 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000263 case kQuad_SegType:
264 SkEvalQuadAt(pts, t, pos, tangent);
265 if (tangent) {
266 tangent->normalize();
267 }
268 break;
269 case kCubic_SegType:
270 SkEvalCubicAt(pts, t, pos, tangent, NULL);
271 if (tangent) {
272 tangent->normalize();
273 }
274 break;
275 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000276 SkDEBUGFAIL("unknown segType");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000277 }
278}
279
reed@google.com5b941532012-05-17 15:31:43 +0000280static void seg_to(const SkPoint pts[], int segType,
281 SkScalar startT, SkScalar stopT, SkPath* dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000282 SkASSERT(startT >= 0 && startT <= SK_Scalar1);
283 SkASSERT(stopT >= 0 && stopT <= SK_Scalar1);
284 SkASSERT(startT <= stopT);
285
286 if (SkScalarNearlyZero(stopT - startT)) {
287 return;
288 }
289
reed@android.com8a1c16f2008-12-17 15:59:43 +0000290 SkPoint tmp0[7], tmp1[7];
291
292 switch (segType) {
293 case kLine_SegType:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000294 if (stopT == kMaxTValue) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000295 dst->lineTo(pts[1]);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296 } else {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000297 dst->lineTo(SkScalarInterp(pts[0].fX, pts[1].fX, stopT),
298 SkScalarInterp(pts[0].fY, pts[1].fY, stopT));
reed@android.com8a1c16f2008-12-17 15:59:43 +0000299 }
300 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000301 case kQuad_SegType:
302 if (startT == 0) {
303 if (stopT == SK_Scalar1) {
304 dst->quadTo(pts[1], pts[2]);
305 } else {
306 SkChopQuadAt(pts, tmp0, stopT);
307 dst->quadTo(tmp0[1], tmp0[2]);
308 }
309 } else {
310 SkChopQuadAt(pts, tmp0, startT);
311 if (stopT == SK_Scalar1) {
312 dst->quadTo(tmp0[3], tmp0[4]);
313 } else {
314 SkChopQuadAt(&tmp0[2], tmp1, SkScalarDiv(stopT - startT,
315 SK_Scalar1 - startT));
316 dst->quadTo(tmp1[1], tmp1[2]);
317 }
318 }
319 break;
320 case kCubic_SegType:
321 if (startT == 0) {
322 if (stopT == SK_Scalar1) {
323 dst->cubicTo(pts[1], pts[2], pts[3]);
324 } else {
325 SkChopCubicAt(pts, tmp0, stopT);
326 dst->cubicTo(tmp0[1], tmp0[2], tmp0[3]);
327 }
328 } else {
329 SkChopCubicAt(pts, tmp0, startT);
330 if (stopT == SK_Scalar1) {
331 dst->cubicTo(tmp0[4], tmp0[5], tmp0[6]);
332 } else {
333 SkChopCubicAt(&tmp0[3], tmp1, SkScalarDiv(stopT - startT,
334 SK_Scalar1 - startT));
335 dst->cubicTo(tmp1[1], tmp1[2], tmp1[3]);
336 }
337 }
338 break;
339 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000340 SkDEBUGFAIL("unknown segType");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000341 sk_throw();
342 }
343}
344
345////////////////////////////////////////////////////////////////////////////////
346////////////////////////////////////////////////////////////////////////////////
347
348SkPathMeasure::SkPathMeasure() {
349 fPath = NULL;
350 fLength = -1; // signal we need to compute it
351 fForceClosed = false;
352 fFirstPtIndex = -1;
353}
354
355SkPathMeasure::SkPathMeasure(const SkPath& path, bool forceClosed) {
356 fPath = &path;
357 fLength = -1; // signal we need to compute it
358 fForceClosed = forceClosed;
359 fFirstPtIndex = -1;
360
361 fIter.setPath(path, forceClosed);
362}
363
364SkPathMeasure::~SkPathMeasure() {}
365
366/** Assign a new path, or null to have none.
367*/
368void SkPathMeasure::setPath(const SkPath* path, bool forceClosed) {
369 fPath = path;
370 fLength = -1; // signal we need to compute it
371 fForceClosed = forceClosed;
372 fFirstPtIndex = -1;
373
374 if (path) {
375 fIter.setPath(*path, forceClosed);
376 }
377 fSegments.reset();
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000378 fPts.reset();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
381SkScalar SkPathMeasure::getLength() {
382 if (fPath == NULL) {
383 return 0;
384 }
385 if (fLength < 0) {
386 this->buildSegments();
387 }
388 SkASSERT(fLength >= 0);
389 return fLength;
390}
391
392const SkPathMeasure::Segment* SkPathMeasure::distanceToSegment(
393 SkScalar distance, SkScalar* t) {
394 SkDEBUGCODE(SkScalar length = ) this->getLength();
395 SkASSERT(distance >= 0 && distance <= length);
396
397 const Segment* seg = fSegments.begin();
398 int count = fSegments.count();
399
400 int index = SkTSearch<SkScalar>(&seg->fDistance, count, distance,
401 sizeof(Segment));
402 // don't care if we hit an exact match or not, so we xor index if it is negative
403 index ^= (index >> 31);
404 seg = &seg[index];
405
406 // now interpolate t-values with the prev segment (if possible)
407 SkScalar startT = 0, startD = 0;
408 // check if the prev segment is legal, and references the same set of points
409 if (index > 0) {
410 startD = seg[-1].fDistance;
411 if (seg[-1].fPtIndex == seg->fPtIndex) {
412 SkASSERT(seg[-1].fType == seg->fType);
413 startT = seg[-1].getScalarT();
414 }
415 }
416
417 SkASSERT(seg->getScalarT() > startT);
418 SkASSERT(distance >= startD);
419 SkASSERT(seg->fDistance > startD);
420
421 *t = startT + SkScalarMulDiv(seg->getScalarT() - startT,
422 distance - startD,
423 seg->fDistance - startD);
424 return seg;
425}
426
427bool SkPathMeasure::getPosTan(SkScalar distance, SkPoint* pos,
428 SkVector* tangent) {
429 SkASSERT(fPath);
430 if (fPath == NULL) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431 return false;
432 }
433
434 SkScalar length = this->getLength(); // call this to force computing it
435 int count = fSegments.count();
436
437 if (count == 0 || length == 0) {
schenney@chromium.orga6d04d92012-01-18 18:02:10 +0000438 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439 }
440
441 // pin the distance to a legal range
442 if (distance < 0) {
443 distance = 0;
444 } else if (distance > length) {
445 distance = length;
446 }
447
448 SkScalar t;
449 const Segment* seg = this->distanceToSegment(distance, &t);
450
reed@google.com5b941532012-05-17 15:31:43 +0000451 compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000452 return true;
453}
454
455bool SkPathMeasure::getMatrix(SkScalar distance, SkMatrix* matrix,
456 MatrixFlags flags) {
457 SkPoint position;
458 SkVector tangent;
459
460 if (this->getPosTan(distance, &position, &tangent)) {
461 if (matrix) {
462 if (flags & kGetTangent_MatrixFlag) {
463 matrix->setSinCos(tangent.fY, tangent.fX, 0, 0);
464 } else {
465 matrix->reset();
466 }
467 if (flags & kGetPosition_MatrixFlag) {
468 matrix->postTranslate(position.fX, position.fY);
469 }
470 }
471 return true;
472 }
473 return false;
474}
475
476bool SkPathMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst,
477 bool startWithMoveTo) {
478 SkASSERT(dst);
479
480 SkScalar length = this->getLength(); // ensure we have built our segments
481
482 if (startD < 0) {
483 startD = 0;
484 }
485 if (stopD > length) {
486 stopD = length;
487 }
488 if (startD >= stopD) {
489 return false;
490 }
491
492 SkPoint p;
493 SkScalar startT, stopT;
494 const Segment* seg = this->distanceToSegment(startD, &startT);
495 const Segment* stopSeg = this->distanceToSegment(stopD, &stopT);
496 SkASSERT(seg <= stopSeg);
497
498 if (startWithMoveTo) {
reed@google.com5b941532012-05-17 15:31:43 +0000499 compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, startT, &p, NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000500 dst->moveTo(p);
501 }
502
503 if (seg->fPtIndex == stopSeg->fPtIndex) {
reed@google.com5b941532012-05-17 15:31:43 +0000504 seg_to(&fPts[seg->fPtIndex], seg->fType, startT, stopT, dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000505 } else {
506 do {
reed@google.com5b941532012-05-17 15:31:43 +0000507 seg_to(&fPts[seg->fPtIndex], seg->fType, startT, SK_Scalar1, dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000508 seg = SkPathMeasure::NextSegment(seg);
509 startT = 0;
510 } while (seg->fPtIndex < stopSeg->fPtIndex);
reed@google.com5b941532012-05-17 15:31:43 +0000511 seg_to(&fPts[seg->fPtIndex], seg->fType, 0, stopT, dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000512 }
513 return true;
514}
515
516bool SkPathMeasure::isClosed() {
517 (void)this->getLength();
518 return fIsClosed;
519}
520
521/** Move to the next contour in the path. Return true if one exists, or false if
522 we're done with the path.
523*/
524bool SkPathMeasure::nextContour() {
525 fLength = -1;
526 return this->getLength() > 0;
527}
528
529///////////////////////////////////////////////////////////////////////////////
530///////////////////////////////////////////////////////////////////////////////
531
532#ifdef SK_DEBUG
533
534void SkPathMeasure::dump() {
535 SkDebugf("pathmeas: length=%g, segs=%d\n", fLength, fSegments.count());
536
537 for (int i = 0; i < fSegments.count(); i++) {
538 const Segment* seg = &fSegments[i];
539 SkDebugf("pathmeas: seg[%d] distance=%g, point=%d, t=%g, type=%d\n",
540 i, seg->fDistance, seg->fPtIndex, seg->getScalarT(),
541 seg->fType);
542 }
543}
544
reed@android.com8a1c16f2008-12-17 15:59:43 +0000545#endif