2 * Copyright 2018 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "include/core/SkContourMeasure.h"
9 #include "include/core/SkPath.h"
10 #include "src/core/SkGeometry.h"
11 #include "src/core/SkPathMeasurePriv.h"
12 #include "src/core/SkPathPriv.h"
13 #include "src/core/SkTSearch.h"
15 #define kMaxTValue 0x3FFFFFFF
17 constexpr static inline SkScalar tValue2Scalar(int t) {
18 SkASSERT((unsigned)t <= kMaxTValue);
19 // 1/kMaxTValue can't be represented as a float, but it's close and the limits work fine.
20 const SkScalar kMaxTReciprocal = 1.0f / (SkScalar)kMaxTValue;
21 return t * kMaxTReciprocal;
24 static_assert(0.0f == tValue2Scalar( 0), "Lower limit should be exact.");
25 static_assert(1.0f == tValue2Scalar(kMaxTValue), "Upper limit should be exact.");
27 SkScalar SkContourMeasure::Segment::getScalarT() const {
28 return tValue2Scalar(fTValue);
31 void SkContourMeasure_segTo(const SkPoint pts[], unsigned segType,
32 SkScalar startT, SkScalar stopT, SkPath* dst) {
33 SkASSERT(startT >= 0 && startT <= SK_Scalar1);
34 SkASSERT(stopT >= 0 && stopT <= SK_Scalar1);
35 SkASSERT(startT <= stopT);
37 if (startT == stopT) {
38 if (!dst->isEmpty()) {
39 /* if the dash as a zero-length on segment, add a corresponding zero-length line.
40 The stroke code will add end caps to zero length lines as appropriate */
42 SkAssertResult(dst->getLastPt(&lastPt));
48 SkPoint tmp0[7], tmp1[7];
52 if (SK_Scalar1 == stopT) {
55 dst->lineTo(SkScalarInterp(pts[0].fX, pts[1].fX, stopT),
56 SkScalarInterp(pts[0].fY, pts[1].fY, stopT));
61 if (SK_Scalar1 == stopT) {
62 dst->quadTo(pts[1], pts[2]);
64 SkChopQuadAt(pts, tmp0, stopT);
65 dst->quadTo(tmp0[1], tmp0[2]);
68 SkChopQuadAt(pts, tmp0, startT);
69 if (SK_Scalar1 == stopT) {
70 dst->quadTo(tmp0[3], tmp0[4]);
72 SkChopQuadAt(&tmp0[2], tmp1, (stopT - startT) / (1 - startT));
73 dst->quadTo(tmp1[1], tmp1[2]);
77 case kConic_SegType: {
78 SkConic conic(pts[0], pts[2], pts[3], pts[1].fX);
81 if (SK_Scalar1 == stopT) {
82 dst->conicTo(conic.fPts[1], conic.fPts[2], conic.fW);
85 if (conic.chopAt(stopT, tmp)) {
86 dst->conicTo(tmp[0].fPts[1], tmp[0].fPts[2], tmp[0].fW);
90 if (SK_Scalar1 == stopT) {
92 if (conic.chopAt(startT, tmp)) {
93 dst->conicTo(tmp[1].fPts[1], tmp[1].fPts[2], tmp[1].fW);
97 conic.chopAt(startT, stopT, &tmp);
98 dst->conicTo(tmp.fPts[1], tmp.fPts[2], tmp.fW);
104 if (SK_Scalar1 == stopT) {
105 dst->cubicTo(pts[1], pts[2], pts[3]);
107 SkChopCubicAt(pts, tmp0, stopT);
108 dst->cubicTo(tmp0[1], tmp0[2], tmp0[3]);
111 SkChopCubicAt(pts, tmp0, startT);
112 if (SK_Scalar1 == stopT) {
113 dst->cubicTo(tmp0[4], tmp0[5], tmp0[6]);
115 SkChopCubicAt(&tmp0[3], tmp1, (stopT - startT) / (1 - startT));
116 dst->cubicTo(tmp1[1], tmp1[2], tmp1[3]);
121 SK_ABORT("unknown segType");
125 ///////////////////////////////////////////////////////////////////////////////
127 static inline int tspan_big_enough(int tspan) {
128 SkASSERT((unsigned)tspan <= kMaxTValue);
132 // can't use tangents, since we need [0..1..................2] to be seen
133 // as definitely not a line (it is when drawn, but not parametrically)
134 // so we compare midpoints
135 #define CHEAP_DIST_LIMIT (SK_Scalar1/2) // just made this value up
137 static bool quad_too_curvy(const SkPoint pts[3], SkScalar tolerance) {
138 // diff = (a/4 + b/2 + c/4) - (a/2 + c/2)
139 // diff = -a/4 + b/2 - c/4
140 SkScalar dx = SkScalarHalf(pts[1].fX) -
141 SkScalarHalf(SkScalarHalf(pts[0].fX + pts[2].fX));
142 SkScalar dy = SkScalarHalf(pts[1].fY) -
143 SkScalarHalf(SkScalarHalf(pts[0].fY + pts[2].fY));
145 SkScalar dist = std::max(SkScalarAbs(dx), SkScalarAbs(dy));
146 return dist > tolerance;
149 static bool conic_too_curvy(const SkPoint& firstPt, const SkPoint& midTPt,
150 const SkPoint& lastPt, SkScalar tolerance) {
151 SkPoint midEnds = firstPt + lastPt;
153 SkVector dxy = midTPt - midEnds;
154 SkScalar dist = std::max(SkScalarAbs(dxy.fX), SkScalarAbs(dxy.fY));
155 return dist > tolerance;
158 static bool cheap_dist_exceeds_limit(const SkPoint& pt, SkScalar x, SkScalar y,
159 SkScalar tolerance) {
160 SkScalar dist = std::max(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY));
161 // just made up the 1/2
162 return dist > tolerance;
165 static bool cubic_too_curvy(const SkPoint pts[4], SkScalar tolerance) {
166 return cheap_dist_exceeds_limit(pts[1],
167 SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1/3),
168 SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1/3), tolerance)
170 cheap_dist_exceeds_limit(pts[2],
171 SkScalarInterp(pts[0].fX, pts[3].fX, SK_Scalar1*2/3),
172 SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3), tolerance);
175 class SkContourMeasureIter::Impl {
177 Impl(const SkPath& path, bool forceClosed, SkScalar resScale)
179 , fIter(SkPathPriv::Iterate(fPath).begin())
180 , fTolerance(CHEAP_DIST_LIMIT * SkScalarInvert(resScale))
181 , fForceClosed(forceClosed) {}
183 bool hasNextSegments() const { return fIter != SkPathPriv::Iterate(fPath).end(); }
184 SkContourMeasure* buildSegments();
188 SkPathPriv::RangeIter fIter;
193 SkTDArray<SkContourMeasure::Segment> fSegments;
194 SkTDArray<SkPoint> fPts; // Points used to define the segments
196 SkDEBUGCODE(void validate() const;)
197 SkScalar compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance, unsigned ptIndex);
198 SkScalar compute_quad_segs(const SkPoint pts[3], SkScalar distance,
199 int mint, int maxt, unsigned ptIndex);
200 SkScalar compute_conic_segs(const SkConic& conic, SkScalar distance,
201 int mint, const SkPoint& minPt,
202 int maxt, const SkPoint& maxPt,
204 SkScalar compute_cubic_segs(const SkPoint pts[4], SkScalar distance,
205 int mint, int maxt, unsigned ptIndex);
208 SkScalar SkContourMeasureIter::Impl::compute_quad_segs(const SkPoint pts[3], SkScalar distance,
209 int mint, int maxt, unsigned ptIndex) {
210 if (tspan_big_enough(maxt - mint) && quad_too_curvy(pts, fTolerance)) {
212 int halft = (mint + maxt) >> 1;
214 SkChopQuadAtHalf(pts, tmp);
215 distance = this->compute_quad_segs(tmp, distance, mint, halft, ptIndex);
216 distance = this->compute_quad_segs(&tmp[2], distance, halft, maxt, ptIndex);
218 SkScalar d = SkPoint::Distance(pts[0], pts[2]);
219 SkScalar prevD = distance;
221 if (distance > prevD) {
222 SkASSERT(ptIndex < (unsigned)fPts.count());
223 SkContourMeasure::Segment* seg = fSegments.append();
224 seg->fDistance = distance;
225 seg->fPtIndex = ptIndex;
226 seg->fType = kQuad_SegType;
233 SkScalar SkContourMeasureIter::Impl::compute_conic_segs(const SkConic& conic, SkScalar distance,
234 int mint, const SkPoint& minPt,
235 int maxt, const SkPoint& maxPt,
237 int halft = (mint + maxt) >> 1;
238 SkPoint halfPt = conic.evalAt(tValue2Scalar(halft));
239 if (!halfPt.isFinite()) {
242 if (tspan_big_enough(maxt - mint) && conic_too_curvy(minPt, halfPt, maxPt, fTolerance)) {
243 distance = this->compute_conic_segs(conic, distance, mint, minPt, halft, halfPt, ptIndex);
244 distance = this->compute_conic_segs(conic, distance, halft, halfPt, maxt, maxPt, ptIndex);
246 SkScalar d = SkPoint::Distance(minPt, maxPt);
247 SkScalar prevD = distance;
249 if (distance > prevD) {
250 SkASSERT(ptIndex < (unsigned)fPts.count());
251 SkContourMeasure::Segment* seg = fSegments.append();
252 seg->fDistance = distance;
253 seg->fPtIndex = ptIndex;
254 seg->fType = kConic_SegType;
261 SkScalar SkContourMeasureIter::Impl::compute_cubic_segs(const SkPoint pts[4], SkScalar distance,
262 int mint, int maxt, unsigned ptIndex) {
263 if (tspan_big_enough(maxt - mint) && cubic_too_curvy(pts, fTolerance)) {
265 int halft = (mint + maxt) >> 1;
267 SkChopCubicAtHalf(pts, tmp);
268 distance = this->compute_cubic_segs(tmp, distance, mint, halft, ptIndex);
269 distance = this->compute_cubic_segs(&tmp[3], distance, halft, maxt, ptIndex);
271 SkScalar d = SkPoint::Distance(pts[0], pts[3]);
272 SkScalar prevD = distance;
274 if (distance > prevD) {
275 SkASSERT(ptIndex < (unsigned)fPts.count());
276 SkContourMeasure::Segment* seg = fSegments.append();
277 seg->fDistance = distance;
278 seg->fPtIndex = ptIndex;
279 seg->fType = kCubic_SegType;
286 SkScalar SkContourMeasureIter::Impl::compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance,
288 SkScalar d = SkPoint::Distance(p0, p1);
290 SkScalar prevD = distance;
292 if (distance > prevD) {
293 SkASSERT((unsigned)ptIndex < (unsigned)fPts.count());
294 SkContourMeasure::Segment* seg = fSegments.append();
295 seg->fDistance = distance;
296 seg->fPtIndex = ptIndex;
297 seg->fType = kLine_SegType;
298 seg->fTValue = kMaxTValue;
304 void SkContourMeasureIter::Impl::validate() const {
305 const SkContourMeasure::Segment* seg = fSegments.begin();
306 const SkContourMeasure::Segment* stop = fSegments.end();
307 unsigned ptIndex = 0;
308 SkScalar distance = 0;
309 // limit the loop to a reasonable number; pathological cases can run for minutes
310 int maxChecks = 10000000; // set to INT_MAX to defeat the check
312 SkASSERT(seg->fDistance > distance);
313 SkASSERT(seg->fPtIndex >= ptIndex);
314 SkASSERT(seg->fTValue > 0);
316 const SkContourMeasure::Segment* s = seg;
317 while (s < stop - 1 && s[0].fPtIndex == s[1].fPtIndex && --maxChecks > 0) {
318 SkASSERT(s[0].fType == s[1].fType);
319 SkASSERT(s[0].fTValue < s[1].fTValue);
323 distance = seg->fDistance;
324 ptIndex = seg->fPtIndex;
330 SkContourMeasure* SkContourMeasureIter::Impl::buildSegments() {
332 SkScalar distance = 0;
333 bool haveSeenClose = fForceClosed;
334 bool haveSeenMoveTo = false;
337 * as we accumulate distance, we have to check that the result of +=
338 * actually made it larger, since a very small delta might be > 0, but
339 * still have no effect on distance (if distance >>> delta).
341 * We do this check below, and in compute_quad_segs and compute_cubic_segs
347 auto end = SkPathPriv::Iterate(fPath).end();
348 for (; fIter != end; ++fIter) {
349 auto [verb, pts, w] = *fIter;
350 if (haveSeenMoveTo && verb == SkPathVerb::kMove) {
354 case SkPathVerb::kMove:
357 SkASSERT(!haveSeenMoveTo);
358 haveSeenMoveTo = true;
361 case SkPathVerb::kLine: {
362 SkASSERT(haveSeenMoveTo);
363 SkScalar prevD = distance;
364 distance = this->compute_line_seg(pts[0], pts[1], distance, ptIndex);
365 if (distance > prevD) {
366 fPts.append(1, pts + 1);
371 case SkPathVerb::kQuad: {
372 SkASSERT(haveSeenMoveTo);
373 SkScalar prevD = distance;
374 distance = this->compute_quad_segs(pts, distance, 0, kMaxTValue, ptIndex);
375 if (distance > prevD) {
376 fPts.append(2, pts + 1);
381 case SkPathVerb::kConic: {
382 SkASSERT(haveSeenMoveTo);
383 const SkConic conic(pts, *w);
384 SkScalar prevD = distance;
385 distance = this->compute_conic_segs(conic, distance, 0, conic.fPts[0],
386 kMaxTValue, conic.fPts[2], ptIndex);
387 if (distance > prevD) {
388 // we store the conic weight in our next point, followed by the last 2 pts
389 // thus to reconstitue a conic, you'd need to say
390 // SkConic(pts[0], pts[2], pts[3], weight = pts[1].fX)
391 fPts.append()->set(conic.fW, 0);
392 fPts.append(2, pts + 1);
397 case SkPathVerb::kCubic: {
398 SkASSERT(haveSeenMoveTo);
399 SkScalar prevD = distance;
400 distance = this->compute_cubic_segs(pts, distance, 0, kMaxTValue, ptIndex);
401 if (distance > prevD) {
402 fPts.append(3, pts + 1);
407 case SkPathVerb::kClose:
408 haveSeenClose = true;
414 if (!SkScalarIsFinite(distance)) {
417 if (fSegments.count() == 0) {
422 SkScalar prevD = distance;
423 SkPoint firstPt = fPts[0];
424 distance = this->compute_line_seg(fPts[ptIndex], firstPt, distance, ptIndex);
425 if (distance > prevD) {
426 *fPts.append() = firstPt;
430 SkDEBUGCODE(this->validate();)
432 return new SkContourMeasure(std::move(fSegments), std::move(fPts), distance, haveSeenClose);
435 static void compute_pos_tan(const SkPoint pts[], unsigned segType,
436 SkScalar t, SkPoint* pos, SkVector* tangent) {
440 pos->set(SkScalarInterp(pts[0].fX, pts[1].fX, t),
441 SkScalarInterp(pts[0].fY, pts[1].fY, t));
444 tangent->setNormalize(pts[1].fX - pts[0].fX, pts[1].fY - pts[0].fY);
448 SkEvalQuadAt(pts, t, pos, tangent);
450 tangent->normalize();
453 case kConic_SegType: {
454 SkConic(pts[0], pts[2], pts[3], pts[1].fX).evalAt(t, pos, tangent);
456 tangent->normalize();
460 SkEvalCubicAt(pts, t, pos, tangent, nullptr);
462 tangent->normalize();
466 SkDEBUGFAIL("unknown segType");
471 ////////////////////////////////////////////////////////////////////////////////
472 ////////////////////////////////////////////////////////////////////////////////
474 SkContourMeasureIter::SkContourMeasureIter() {
477 SkContourMeasureIter::SkContourMeasureIter(const SkPath& path, bool forceClosed,
479 this->reset(path, forceClosed, resScale);
482 SkContourMeasureIter::~SkContourMeasureIter() {}
484 /** Assign a new path, or null to have none.
486 void SkContourMeasureIter::reset(const SkPath& path, bool forceClosed, SkScalar resScale) {
487 if (path.isFinite()) {
488 fImpl = std::make_unique<Impl>(path, forceClosed, resScale);
494 sk_sp<SkContourMeasure> SkContourMeasureIter::next() {
498 while (fImpl->hasNextSegments()) {
499 auto cm = fImpl->buildSegments();
501 return sk_sp<SkContourMeasure>(cm);
507 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
509 SkContourMeasure::SkContourMeasure(SkTDArray<Segment>&& segs, SkTDArray<SkPoint>&& pts, SkScalar length, bool isClosed)
510 : fSegments(std::move(segs))
511 , fPts(std::move(pts))
513 , fIsClosed(isClosed)
516 template <typename T, typename K>
517 int SkTKSearch(const T base[], int count, const K& key) {
518 SkASSERT(count >= 0);
523 SkASSERT(base != nullptr); // base may be nullptr if count is zero
526 unsigned hi = count - 1;
529 unsigned mid = (hi + lo) >> 1;
530 if (base[mid].fDistance < key) {
537 if (base[hi].fDistance < key) {
540 } else if (key < base[hi].fDistance) {
546 const SkContourMeasure::Segment* SkContourMeasure::distanceToSegment( SkScalar distance,
548 SkDEBUGCODE(SkScalar length = ) this->length();
549 SkASSERT(distance >= 0 && distance <= length);
551 const Segment* seg = fSegments.begin();
552 int count = fSegments.count();
554 int index = SkTKSearch<Segment, SkScalar>(seg, count, distance);
555 // don't care if we hit an exact match or not, so we xor index if it is negative
556 index ^= (index >> 31);
559 // now interpolate t-values with the prev segment (if possible)
560 SkScalar startT = 0, startD = 0;
561 // check if the prev segment is legal, and references the same set of points
563 startD = seg[-1].fDistance;
564 if (seg[-1].fPtIndex == seg->fPtIndex) {
565 SkASSERT(seg[-1].fType == seg->fType);
566 startT = seg[-1].getScalarT();
570 SkASSERT(seg->getScalarT() > startT);
571 SkASSERT(distance >= startD);
572 SkASSERT(seg->fDistance > startD);
574 *t = startT + (seg->getScalarT() - startT) * (distance - startD) / (seg->fDistance - startD);
578 bool SkContourMeasure::getPosTan(SkScalar distance, SkPoint* pos, SkVector* tangent) const {
579 if (SkScalarIsNaN(distance)) {
583 const SkScalar length = this->length();
584 SkASSERT(length > 0 && fSegments.count() > 0);
586 // pin the distance to a legal range
589 } else if (distance > length) {
594 const Segment* seg = this->distanceToSegment(distance, &t);
595 if (SkScalarIsNaN(t)) {
599 SkASSERT((unsigned)seg->fPtIndex < (unsigned)fPts.count());
600 compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent);
604 bool SkContourMeasure::getMatrix(SkScalar distance, SkMatrix* matrix, MatrixFlags flags) const {
608 if (this->getPosTan(distance, &position, &tangent)) {
610 if (flags & kGetTangent_MatrixFlag) {
611 matrix->setSinCos(tangent.fY, tangent.fX, 0, 0);
615 if (flags & kGetPosition_MatrixFlag) {
616 matrix->postTranslate(position.fX, position.fY);
624 bool SkContourMeasure::getSegment(SkScalar startD, SkScalar stopD, SkPath* dst,
625 bool startWithMoveTo) const {
628 SkScalar length = this->length(); // ensure we have built our segments
633 if (stopD > length) {
636 if (!(startD <= stopD)) { // catch NaN values as well
639 if (!fSegments.count()) {
644 SkScalar startT, stopT;
645 const Segment* seg = this->distanceToSegment(startD, &startT);
646 if (!SkScalarIsFinite(startT)) {
649 const Segment* stopSeg = this->distanceToSegment(stopD, &stopT);
650 if (!SkScalarIsFinite(stopT)) {
653 SkASSERT(seg <= stopSeg);
654 if (startWithMoveTo) {
655 compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, startT, &p, nullptr);
659 if (seg->fPtIndex == stopSeg->fPtIndex) {
660 SkContourMeasure_segTo(&fPts[seg->fPtIndex], seg->fType, startT, stopT, dst);
663 SkContourMeasure_segTo(&fPts[seg->fPtIndex], seg->fType, startT, SK_Scalar1, dst);
664 seg = SkContourMeasure::Segment::Next(seg);
666 } while (seg->fPtIndex < stopSeg->fPtIndex);
667 SkContourMeasure_segTo(&fPts[seg->fPtIndex], seg->fType, 0, stopT, dst);