1 /*
2 * Copyright 2008 The Android Open Source Project
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 "src/core/SkStroke.h"
9
10 #include "include/core/SkPath.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkScalar.h"
14 #include "include/private/base/SkFloatingPoint.h"
15 #include "include/private/base/SkMacros.h"
16 #include "include/private/base/SkTo.h"
17 #include "src/core/SkGeometry.h"
18 #include "src/core/SkPathEnums.h"
19 #include "src/core/SkPathPriv.h"
20 #include "src/core/SkPointPriv.h"
21 #include "src/core/SkStrokerPriv.h"
22
23 #include <algorithm>
24 #include <array>
25
26 enum {
27 kTangent_RecursiveLimit,
28 kCubic_RecursiveLimit,
29 kConic_RecursiveLimit,
30 kQuad_RecursiveLimit
31 };
32
33 // quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
34 // largest seen for normal cubics : 5, 26
35 // largest seen for normal quads : 11
36 // 3x limits seen in practice, except for cubics (3x limit would be ~75).
37 // For cubics, we never get close to 75 when running through dm. The limit of 24
38 // was chosen because it's close to the peak in a count of cubic recursion depths visited
39 // (define DEBUG_CUBIC_RECURSION_DEPTHS) and no diffs were produced on gold when using it.
40 static const int kRecursiveLimits[] = { 5*3, 24, 11*3, 11*3 };
41
42 static_assert(0 == kTangent_RecursiveLimit, "cubic_stroke_relies_on_tangent_equalling_zero");
43 static_assert(1 == kCubic_RecursiveLimit, "cubic_stroke_relies_on_cubic_equalling_one");
44 static_assert(std::size(kRecursiveLimits) == kQuad_RecursiveLimit + 1,
45 "recursive_limits_mismatch");
46
47 #if defined SK_DEBUG && QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
48 int gMaxRecursion[std::size(kRecursiveLimits)] = { 0 };
49 #endif
50 #ifndef DEBUG_QUAD_STROKER
51 #define DEBUG_QUAD_STROKER 0
52 #endif
53
54 #if DEBUG_QUAD_STROKER
55 /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting
56 stroke has more than the optimal number of quadratics and lines */
57 #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
58 SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \
59 SkDebugf(" " #resultType " t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \
60 resultType
61 #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__
62 #else
63 #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
64 resultType
65 #define STROKER_DEBUG_PARAMS(...)
66 #endif
67
68 #ifndef DEBUG_CUBIC_RECURSION_DEPTHS
69 #define DEBUG_CUBIC_RECURSION_DEPTHS 0
70 #endif
71 #if DEBUG_CUBIC_RECURSION_DEPTHS
72 /* Prints a histogram of recursion depths at process termination. */
73 static struct DepthHistogram {
74 inline static constexpr int kMaxDepth = 75;
75 int fCubicDepths[kMaxDepth + 1];
76
DepthHistogramDepthHistogram77 DepthHistogram() { memset(fCubicDepths, 0, sizeof(fCubicDepths)); }
78
~DepthHistogramDepthHistogram79 ~DepthHistogram() {
80 SkDebugf("# times recursion terminated per depth:\n");
81 for (int i = 0; i <= kMaxDepth; i++) {
82 SkDebugf(" depth %d: %d\n", i, fCubicDepths[i]);
83 }
84 }
85
incDepthDepthHistogram86 inline void incDepth(int depth) {
87 SkASSERT(depth >= 0 && depth <= kMaxDepth);
88 fCubicDepths[depth]++;
89 }
90 } sCubicDepthHistogram;
91
92 #define DEBUG_CUBIC_RECURSION_TRACK_DEPTH(depth) sCubicDepthHistogram.incDepth(depth)
93 #else
94 #define DEBUG_CUBIC_RECURSION_TRACK_DEPTH(depth) (void)(depth)
95 #endif
96
degenerate_vector(const SkVector & v)97 static inline bool degenerate_vector(const SkVector& v) {
98 return !SkPointPriv::CanNormalize(v.fX, v.fY);
99 }
100
set_normal_unitnormal(const SkPoint & before,const SkPoint & after,SkScalar scale,SkScalar radius,SkVector * normal,SkVector * unitNormal)101 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, SkScalar scale,
102 SkScalar radius,
103 SkVector* normal, SkVector* unitNormal) {
104 if (!unitNormal->setNormalize((after.fX - before.fX) * scale,
105 (after.fY - before.fY) * scale)) {
106 return false;
107 }
108 SkPointPriv::RotateCCW(unitNormal);
109 unitNormal->scale(radius, normal);
110 return true;
111 }
112
set_normal_unitnormal(const SkVector & vec,SkScalar radius,SkVector * normal,SkVector * unitNormal)113 static bool set_normal_unitnormal(const SkVector& vec,
114 SkScalar radius,
115 SkVector* normal, SkVector* unitNormal) {
116 if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
117 return false;
118 }
119 SkPointPriv::RotateCCW(unitNormal);
120 unitNormal->scale(radius, normal);
121 return true;
122 }
123
124 ///////////////////////////////////////////////////////////////////////////////
125
126 struct SkQuadConstruct { // The state of the quad stroke under construction.
127 SkPoint fQuad[3]; // the stroked quad parallel to the original curve
128 SkVector fTangentStart; // tangent vector at fQuad[0]
129 SkVector fTangentEnd; // tangent vector at fQuad[2]
130 SkScalar fStartT; // a segment of the original curve
131 SkScalar fMidT; // "
132 SkScalar fEndT; // "
133 bool fStartSet; // state to share common points across structs
134 bool fEndSet; // "
135 bool fOppositeTangents; // set if coincident tangents have opposite directions
136
137 // return false if start and end are too close to have a unique middle
initSkQuadConstruct138 bool init(SkScalar start, SkScalar end) {
139 fStartT = start;
140 fMidT = (start + end) * SK_ScalarHalf;
141 fEndT = end;
142 fStartSet = fEndSet = false;
143 return fStartT < fMidT && fMidT < fEndT;
144 }
145
initWithStartSkQuadConstruct146 bool initWithStart(SkQuadConstruct* parent) {
147 if (!init(parent->fStartT, parent->fMidT)) {
148 return false;
149 }
150 fQuad[0] = parent->fQuad[0];
151 fTangentStart = parent->fTangentStart;
152 fStartSet = true;
153 return true;
154 }
155
initWithEndSkQuadConstruct156 bool initWithEnd(SkQuadConstruct* parent) {
157 if (!init(parent->fMidT, parent->fEndT)) {
158 return false;
159 }
160 fQuad[2] = parent->fQuad[2];
161 fTangentEnd = parent->fTangentEnd;
162 fEndSet = true;
163 return true;
164 }
165 };
166
167 class SkPathStroker {
168 public:
169 SkPathStroker(const SkPath& src,
170 SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
171 SkPaint::Join, SkScalar resScale,
172 bool canIgnoreCenter);
173
hasOnlyMoveTo() const174 bool hasOnlyMoveTo() const { return 0 == fSegmentCount; }
moveToPt() const175 SkPoint moveToPt() const { return fFirstPt; }
176
177 void moveTo(const SkPoint&);
178 void lineTo(const SkPoint&, const SkPath::Iter* iter = nullptr);
179 void quadTo(const SkPoint&, const SkPoint&);
180 void conicTo(const SkPoint&, const SkPoint&, SkScalar weight);
181 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
close(bool isLine)182 void close(bool isLine) { this->finishContour(true, isLine); }
183
done(SkPath * dst,bool isLine)184 void done(SkPath* dst, bool isLine) {
185 this->finishContour(false, isLine);
186 dst->swap(fOuter);
187 }
188
getResScale() const189 SkScalar getResScale() const { return fResScale; }
190
isCurrentContourEmpty() const191 bool isCurrentContourEmpty() const {
192 return fInner.isZeroLengthSincePoint(0) &&
193 fOuter.isZeroLengthSincePoint(fFirstOuterPtIndexInContour);
194 }
195
196 private:
197 SkScalar fRadius;
198 SkScalar fInvMiterLimit;
199 SkScalar fResScale;
200 SkScalar fInvResScale;
201 SkScalar fInvResScaleSquared;
202
203 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
204 SkPoint fFirstPt, fPrevPt; // on original path
205 SkPoint fFirstOuterPt;
206 int fFirstOuterPtIndexInContour;
207 int fSegmentCount;
208 bool fPrevIsLine;
209 bool fCanIgnoreCenter;
210
211 SkStrokerPriv::CapProc fCapper;
212 SkStrokerPriv::JoinProc fJoiner;
213
214 SkPath fInner, fOuter, fCusper; // outer is our working answer, inner is temp
215
216 enum StrokeType {
217 kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis
218 kInner_StrokeType = -1
219 } fStrokeType;
220
221 enum ResultType {
222 kSplit_ResultType, // the caller should split the quad stroke in two
223 kDegenerate_ResultType, // the caller should add a line
224 kQuad_ResultType, // the caller should (continue to try to) add a quad stroke
225 };
226
227 enum ReductionType {
228 kPoint_ReductionType, // all curve points are practically identical
229 kLine_ReductionType, // the control point is on the line between the ends
230 kQuad_ReductionType, // the control point is outside the line between the ends
231 kDegenerate_ReductionType, // the control point is on the line but outside the ends
232 kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic)
233 kDegenerate3_ReductionType, // three areas of max curvature found (for cubic)
234 };
235
236 enum IntersectRayType {
237 kCtrlPt_RayType,
238 kResultType_RayType,
239 };
240
241 int fRecursionDepth; // track stack depth to abort if numerics run amok
242 bool fFoundTangents; // do less work until tangents meet (cubic)
243 bool fJoinCompleted; // previous join was not degenerate
244
245 void addDegenerateLine(const SkQuadConstruct* );
246 static ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
247 static ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
248 const SkPoint** tanPtPtr);
249 static ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
250 ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ) const;
251 ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
252 ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
253 void conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt,
254 SkVector* tangent) const;
255 void conicQuadEnds(const SkConic& , SkQuadConstruct* ) const;
256 bool conicStroke(const SkConic& , SkQuadConstruct* );
257 bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
258 void cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
259 SkVector* tangent) const;
260 void cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* );
261 void cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* , SkPoint* mid) const;
262 bool cubicStroke(const SkPoint cubic[4], SkQuadConstruct* );
263 void init(StrokeType strokeType, SkQuadConstruct* , SkScalar tStart, SkScalar tEnd);
264 ResultType intersectRay(SkQuadConstruct* , IntersectRayType STROKER_DEBUG_PARAMS(int) ) const;
265 bool ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const;
266 void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
267 SkPoint* tangent) const;
268 bool quadStroke(const SkPoint quad[3], SkQuadConstruct* );
269 void setConicEndNormal(const SkConic& ,
270 const SkVector& normalAB, const SkVector& unitNormalAB,
271 SkVector* normalBC, SkVector* unitNormalBC);
272 void setCubicEndNormal(const SkPoint cubic[4],
273 const SkVector& normalAB, const SkVector& unitNormalAB,
274 SkVector* normalCD, SkVector* unitNormalCD);
275 void setQuadEndNormal(const SkPoint quad[3],
276 const SkVector& normalAB, const SkVector& unitNormalAB,
277 SkVector* normalBC, SkVector* unitNormalBC);
278 void setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, SkVector* tangent) const;
279 static bool SlightAngle(SkQuadConstruct* );
280 ResultType strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2],
281 SkQuadConstruct* STROKER_DEBUG_PARAMS(int depth) ) const;
282 ResultType tangentsMeet(const SkPoint cubic[4], SkQuadConstruct* );
283
284 void finishContour(bool close, bool isLine);
285 bool preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
286 bool isLine);
287 void postJoinTo(const SkPoint&, const SkVector& normal,
288 const SkVector& unitNormal);
289
290 void line_to(const SkPoint& currPt, const SkVector& normal);
291 };
292
293 ///////////////////////////////////////////////////////////////////////////////
294
preJoinTo(const SkPoint & currPt,SkVector * normal,SkVector * unitNormal,bool currIsLine)295 bool SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
296 SkVector* unitNormal, bool currIsLine) {
297 SkASSERT(fSegmentCount >= 0);
298
299 if (!set_normal_unitnormal(fPrevPt, currPt, fResScale, fRadius, normal, unitNormal)) {
300 if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper) {
301 return false;
302 }
303 /* Square caps and round caps draw even if the segment length is zero.
304 Since the zero length segment has no direction, set the orientation
305 to upright as the default orientation */
306 normal->set(fRadius, 0);
307 unitNormal->set(1, 0);
308 }
309
310 if (fSegmentCount == 0) {
311 fFirstNormal = *normal;
312 fFirstUnitNormal = *unitNormal;
313 fFirstOuterPt = fPrevPt + *normal;
314
315 fOuter.moveTo(fFirstOuterPt);
316 fInner.moveTo(fPrevPt - *normal);
317 } else { // we have a previous segment
318 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
319 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
320 }
321 fPrevIsLine = currIsLine;
322 return true;
323 }
324
postJoinTo(const SkPoint & currPt,const SkVector & normal,const SkVector & unitNormal)325 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
326 const SkVector& unitNormal) {
327 fJoinCompleted = true;
328 fPrevPt = currPt;
329 fPrevUnitNormal = unitNormal;
330 fPrevNormal = normal;
331 fSegmentCount += 1;
332 }
333
finishContour(bool close,bool currIsLine)334 void SkPathStroker::finishContour(bool close, bool currIsLine) {
335 if (fSegmentCount > 0) {
336 SkPoint pt;
337
338 if (close) {
339 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
340 fFirstUnitNormal, fRadius, fInvMiterLimit,
341 fPrevIsLine, currIsLine);
342 fOuter.close();
343
344 if (fCanIgnoreCenter) {
345 // If we can ignore the center just make sure the larger of the two paths
346 // is preserved and don't add the smaller one.
347 if (fInner.getBounds().contains(fOuter.getBounds())) {
348 fInner.swap(fOuter);
349 }
350 } else {
351 // now add fInner as its own contour
352 fInner.getLastPt(&pt);
353 fOuter.moveTo(pt);
354 fOuter.reversePathTo(fInner);
355 fOuter.close();
356 }
357 } else { // add caps to start and end
358 // cap the end
359 fInner.getLastPt(&pt);
360 fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
361 currIsLine ? &fInner : nullptr);
362 fOuter.reversePathTo(fInner);
363 // cap the start
364 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
365 fPrevIsLine ? &fInner : nullptr);
366 fOuter.close();
367 }
368 if (!fCusper.isEmpty()) {
369 fOuter.addPath(fCusper);
370 fCusper.rewind();
371 }
372 }
373 // since we may re-use fInner, we rewind instead of reset, to save on
374 // reallocating its internal storage.
375 fInner.rewind();
376 fSegmentCount = -1;
377 fFirstOuterPtIndexInContour = fOuter.countPoints();
378 }
379
380 ///////////////////////////////////////////////////////////////////////////////
381
SkPathStroker(const SkPath & src,SkScalar radius,SkScalar miterLimit,SkPaint::Cap cap,SkPaint::Join join,SkScalar resScale,bool canIgnoreCenter)382 SkPathStroker::SkPathStroker(const SkPath& src,
383 SkScalar radius, SkScalar miterLimit,
384 SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale,
385 bool canIgnoreCenter)
386 : fRadius(radius)
387 , fResScale(resScale)
388 , fCanIgnoreCenter(canIgnoreCenter) {
389
390 /* This is only used when join is miter_join, but we initialize it here
391 so that it is always defined, to fis valgrind warnings.
392 */
393 fInvMiterLimit = 0;
394
395 if (join == SkPaint::kMiter_Join) {
396 if (miterLimit <= SK_Scalar1) {
397 join = SkPaint::kBevel_Join;
398 } else {
399 fInvMiterLimit = SkScalarInvert(miterLimit);
400 }
401 }
402 fCapper = SkStrokerPriv::CapFactory(cap);
403 fJoiner = SkStrokerPriv::JoinFactory(join);
404 fSegmentCount = -1;
405 fFirstOuterPtIndexInContour = 0;
406 fPrevIsLine = false;
407
408 // Need some estimate of how large our final result (fOuter)
409 // and our per-contour temp (fInner) will be, so we don't spend
410 // extra time repeatedly growing these arrays.
411 //
412 // 3x for result == inner + outer + join (swag)
413 // 1x for inner == 'wag' (worst contour length would be better guess)
414 fOuter.incReserve(src.countPoints() * 3);
415 fOuter.setIsVolatile(true);
416 fInner.incReserve(src.countPoints());
417 fInner.setIsVolatile(true);
418 // TODO : write a common error function used by stroking and filling
419 // The '4' below matches the fill scan converter's error term
420 fInvResScale = SkScalarInvert(resScale * 4);
421 fInvResScaleSquared = fInvResScale * fInvResScale;
422 fRecursionDepth = 0;
423 }
424
moveTo(const SkPoint & pt)425 void SkPathStroker::moveTo(const SkPoint& pt) {
426 if (fSegmentCount > 0) {
427 this->finishContour(false, false);
428 }
429 fSegmentCount = 0;
430 fFirstPt = fPrevPt = pt;
431 fJoinCompleted = false;
432 }
433
line_to(const SkPoint & currPt,const SkVector & normal)434 void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
435 fOuter.lineTo(currPt + normal);
436 fInner.lineTo(currPt - normal);
437 }
438
has_valid_tangent(const SkPath::Iter * iter)439 static bool has_valid_tangent(const SkPath::Iter* iter) {
440 SkPath::Iter copy = *iter;
441 SkPath::Verb verb;
442 SkPoint pts[4];
443 while ((verb = copy.next(pts))) {
444 switch (verb) {
445 case SkPath::kMove_Verb:
446 return false;
447 case SkPath::kLine_Verb:
448 if (pts[0] == pts[1]) {
449 continue;
450 }
451 return true;
452 case SkPath::kQuad_Verb:
453 case SkPath::kConic_Verb:
454 if (pts[0] == pts[1] && pts[0] == pts[2]) {
455 continue;
456 }
457 return true;
458 case SkPath::kCubic_Verb:
459 if (pts[0] == pts[1] && pts[0] == pts[2] && pts[0] == pts[3]) {
460 continue;
461 }
462 return true;
463 case SkPath::kClose_Verb:
464 case SkPath::kDone_Verb:
465 return false;
466 }
467 }
468 return false;
469 }
470
lineTo(const SkPoint & currPt,const SkPath::Iter * iter)471 void SkPathStroker::lineTo(const SkPoint& currPt, const SkPath::Iter* iter) {
472 bool teenyLine = SkPointPriv::EqualsWithinTolerance(fPrevPt, currPt, SK_ScalarNearlyZero * fInvResScale);
473 if (SkStrokerPriv::CapFactory(SkPaint::kButt_Cap) == fCapper && teenyLine) {
474 return;
475 }
476 if (teenyLine && (fJoinCompleted || (iter && has_valid_tangent(iter)))) {
477 return;
478 }
479 SkVector normal, unitNormal;
480
481 if (!this->preJoinTo(currPt, &normal, &unitNormal, true)) {
482 return;
483 }
484 this->line_to(currPt, normal);
485 this->postJoinTo(currPt, normal, unitNormal);
486 }
487
setQuadEndNormal(const SkPoint quad[3],const SkVector & normalAB,const SkVector & unitNormalAB,SkVector * normalBC,SkVector * unitNormalBC)488 void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
489 const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
490 if (!set_normal_unitnormal(quad[1], quad[2], fResScale, fRadius, normalBC, unitNormalBC)) {
491 *normalBC = normalAB;
492 *unitNormalBC = unitNormalAB;
493 }
494 }
495
setConicEndNormal(const SkConic & conic,const SkVector & normalAB,const SkVector & unitNormalAB,SkVector * normalBC,SkVector * unitNormalBC)496 void SkPathStroker::setConicEndNormal(const SkConic& conic, const SkVector& normalAB,
497 const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
498 setQuadEndNormal(conic.fPts, normalAB, unitNormalAB, normalBC, unitNormalBC);
499 }
500
setCubicEndNormal(const SkPoint cubic[4],const SkVector & normalAB,const SkVector & unitNormalAB,SkVector * normalCD,SkVector * unitNormalCD)501 void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB,
502 const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
503 SkVector ab = cubic[1] - cubic[0];
504 SkVector cd = cubic[3] - cubic[2];
505
506 bool degenerateAB = degenerate_vector(ab);
507 bool degenerateCD = degenerate_vector(cd);
508
509 if (degenerateAB && degenerateCD) {
510 goto DEGENERATE_NORMAL;
511 }
512
513 if (degenerateAB) {
514 ab = cubic[2] - cubic[0];
515 degenerateAB = degenerate_vector(ab);
516 }
517 if (degenerateCD) {
518 cd = cubic[3] - cubic[1];
519 degenerateCD = degenerate_vector(cd);
520 }
521 if (degenerateAB || degenerateCD) {
522 DEGENERATE_NORMAL:
523 *normalCD = normalAB;
524 *unitNormalCD = unitNormalAB;
525 return;
526 }
527 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
528 }
529
init(StrokeType strokeType,SkQuadConstruct * quadPts,SkScalar tStart,SkScalar tEnd)530 void SkPathStroker::init(StrokeType strokeType, SkQuadConstruct* quadPts, SkScalar tStart,
531 SkScalar tEnd) {
532 fStrokeType = strokeType;
533 fFoundTangents = false;
534 quadPts->init(tStart, tEnd);
535 }
536
537 // returns the distance squared from the point to the line
pt_to_line(const SkPoint & pt,const SkPoint & lineStart,const SkPoint & lineEnd)538 static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
539 SkVector dxy = lineEnd - lineStart;
540 SkVector ab0 = pt - lineStart;
541 SkScalar numer = dxy.dot(ab0);
542 SkScalar denom = dxy.dot(dxy);
543 SkScalar t = sk_ieee_float_divide(numer, denom);
544 if (t >= 0 && t <= 1) {
545 SkPoint hit = lineStart * (1 - t) + lineEnd * t;
546 return SkPointPriv::DistanceToSqd(hit, pt);
547 } else {
548 return SkPointPriv::DistanceToSqd(pt, lineStart);
549 }
550 }
551
552 // returns the distance squared from the point to the line
pt_to_tangent_line(const SkPoint & pt,const SkPoint & lineStart,const SkVector & tangent)553 static SkScalar pt_to_tangent_line(const SkPoint& pt,
554 const SkPoint& lineStart,
555 const SkVector& tangent) {
556 SkVector dxy = tangent;
557 SkVector ab0 = pt - lineStart;
558 SkScalar numer = dxy.dot(ab0);
559 SkScalar denom = dxy.dot(dxy);
560 SkScalar t = sk_ieee_float_divide(numer, denom);
561 if (t >= 0 && t <= 1) {
562 SkPoint hit = lineStart + tangent * t;
563 return SkPointPriv::DistanceToSqd(hit, pt);
564 } else {
565 return SkPointPriv::DistanceToSqd(pt, lineStart);
566 }
567 }
568
569 /* Given a cubic, determine if all four points are in a line.
570 Return true if the inner points is close to a line connecting the outermost points.
571
572 Find the outermost point by looking for the largest difference in X or Y.
573 Given the indices of the outermost points, and that outer_1 is greater than outer_2,
574 this table shows the index of the smaller of the remaining points:
575
576 outer_2
577 0 1 2 3
578 outer_1 ----------------
579 0 | - 2 1 1
580 1 | - - 0 0
581 2 | - - - 0
582 3 | - - - -
583
584 If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
585
586 This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
587
588 Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
589
590 mid_2 == (outer_1 ^ outer_2 ^ mid_1)
591 */
cubic_in_line(const SkPoint cubic[4])592 static bool cubic_in_line(const SkPoint cubic[4]) {
593 SkScalar ptMax = -1;
594 int outer1 SK_INIT_TO_AVOID_WARNING;
595 int outer2 SK_INIT_TO_AVOID_WARNING;
596 for (int index = 0; index < 3; ++index) {
597 for (int inner = index + 1; inner < 4; ++inner) {
598 SkVector testDiff = cubic[inner] - cubic[index];
599 SkScalar testMax = std::max(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
600 if (ptMax < testMax) {
601 outer1 = index;
602 outer2 = inner;
603 ptMax = testMax;
604 }
605 }
606 }
607 SkASSERT(outer1 >= 0 && outer1 <= 2);
608 SkASSERT(outer2 >= 1 && outer2 <= 3);
609 SkASSERT(outer1 < outer2);
610 int mid1 = (1 + (2 >> outer2)) >> outer1;
611 SkASSERT(mid1 >= 0 && mid1 <= 2);
612 SkASSERT(outer1 != mid1 && outer2 != mid1);
613 int mid2 = outer1 ^ outer2 ^ mid1;
614 SkASSERT(mid2 >= 1 && mid2 <= 3);
615 SkASSERT(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
616 SkASSERT(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
617 SkScalar lineSlop = ptMax * ptMax * 0.00001f; // this multiplier is pulled out of the air
618 return pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= lineSlop
619 && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= lineSlop;
620 }
621
622 /* Given quad, see if all there points are in a line.
623 Return true if the inside point is close to a line connecting the outermost points.
624
625 Find the outermost point by looking for the largest difference in X or Y.
626 Since the XOR of the indices is 3 (0 ^ 1 ^ 2)
627 the missing index equals: outer_1 ^ outer_2 ^ 3
628 */
quad_in_line(const SkPoint quad[3])629 static bool quad_in_line(const SkPoint quad[3]) {
630 SkScalar ptMax = -1;
631 int outer1 SK_INIT_TO_AVOID_WARNING;
632 int outer2 SK_INIT_TO_AVOID_WARNING;
633 for (int index = 0; index < 2; ++index) {
634 for (int inner = index + 1; inner < 3; ++inner) {
635 SkVector testDiff = quad[inner] - quad[index];
636 SkScalar testMax = std::max(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
637 if (ptMax < testMax) {
638 outer1 = index;
639 outer2 = inner;
640 ptMax = testMax;
641 }
642 }
643 }
644 SkASSERT(outer1 >= 0 && outer1 <= 1);
645 SkASSERT(outer2 >= 1 && outer2 <= 2);
646 SkASSERT(outer1 < outer2);
647 int mid = outer1 ^ outer2 ^ 3;
648 const float kCurvatureSlop = 0.000005f; // this multiplier is pulled out of the air
649 SkScalar lineSlop = ptMax * ptMax * kCurvatureSlop;
650 return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
651 }
652
conic_in_line(const SkConic & conic)653 static bool conic_in_line(const SkConic& conic) {
654 return quad_in_line(conic.fPts);
655 }
656
CheckCubicLinear(const SkPoint cubic[4],SkPoint reduction[3],const SkPoint ** tangentPtPtr)657 SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
658 SkPoint reduction[3], const SkPoint** tangentPtPtr) {
659 bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
660 bool degenerateBC = degenerate_vector(cubic[2] - cubic[1]);
661 bool degenerateCD = degenerate_vector(cubic[3] - cubic[2]);
662 if (degenerateAB & degenerateBC & degenerateCD) {
663 return kPoint_ReductionType;
664 }
665 if (degenerateAB + degenerateBC + degenerateCD == 2) {
666 return kLine_ReductionType;
667 }
668 if (!cubic_in_line(cubic)) {
669 *tangentPtPtr = degenerateAB ? &cubic[2] : &cubic[1];
670 return kQuad_ReductionType;
671 }
672 SkScalar tValues[3];
673 int count = SkFindCubicMaxCurvature(cubic, tValues);
674 int rCount = 0;
675 // Now loop over the t-values, and reject any that evaluate to either end-point
676 for (int index = 0; index < count; ++index) {
677 SkScalar t = tValues[index];
678 if (0 >= t || t >= 1) {
679 continue;
680 }
681 SkEvalCubicAt(cubic, t, &reduction[rCount], nullptr, nullptr);
682 if (reduction[rCount] != cubic[0] && reduction[rCount] != cubic[3]) {
683 ++rCount;
684 }
685 }
686 if (rCount == 0) {
687 return kLine_ReductionType;
688 }
689 static_assert(kQuad_ReductionType + 1 == kDegenerate_ReductionType, "enum_out_of_whack");
690 static_assert(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, "enum_out_of_whack");
691 static_assert(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, "enum_out_of_whack");
692
693 return (ReductionType) (kQuad_ReductionType + rCount);
694 }
695
CheckConicLinear(const SkConic & conic,SkPoint * reduction)696 SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic,
697 SkPoint* reduction) {
698 bool degenerateAB = degenerate_vector(conic.fPts[1] - conic.fPts[0]);
699 bool degenerateBC = degenerate_vector(conic.fPts[2] - conic.fPts[1]);
700 if (degenerateAB & degenerateBC) {
701 return kPoint_ReductionType;
702 }
703 if (degenerateAB | degenerateBC) {
704 return kLine_ReductionType;
705 }
706 if (!conic_in_line(conic)) {
707 return kQuad_ReductionType;
708 }
709 // SkFindConicMaxCurvature would be a better solution, once we know how to
710 // implement it. Quad curvature is a reasonable substitute
711 SkScalar t = SkFindQuadMaxCurvature(conic.fPts);
712 if (0 == t || SkIsNaN(t)) {
713 return kLine_ReductionType;
714 }
715 conic.evalAt(t, reduction, nullptr);
716 return kDegenerate_ReductionType;
717 }
718
CheckQuadLinear(const SkPoint quad[3],SkPoint * reduction)719 SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3],
720 SkPoint* reduction) {
721 bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
722 bool degenerateBC = degenerate_vector(quad[2] - quad[1]);
723 if (degenerateAB & degenerateBC) {
724 return kPoint_ReductionType;
725 }
726 if (degenerateAB | degenerateBC) {
727 return kLine_ReductionType;
728 }
729 if (!quad_in_line(quad)) {
730 return kQuad_ReductionType;
731 }
732 SkScalar t = SkFindQuadMaxCurvature(quad);
733 if (0 == t || 1 == t) {
734 return kLine_ReductionType;
735 }
736 *reduction = SkEvalQuadAt(quad, t);
737 return kDegenerate_ReductionType;
738 }
739
conicTo(const SkPoint & pt1,const SkPoint & pt2,SkScalar weight)740 void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) {
741 const SkConic conic(fPrevPt, pt1, pt2, weight);
742 SkPoint reduction;
743 ReductionType reductionType = CheckConicLinear(conic, &reduction);
744 if (kPoint_ReductionType == reductionType) {
745 /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
746 as if it were followed by a zero-length line. Lines without length
747 can have square and round end caps. */
748 this->lineTo(pt2);
749 return;
750 }
751 if (kLine_ReductionType == reductionType) {
752 this->lineTo(pt2);
753 return;
754 }
755 if (kDegenerate_ReductionType == reductionType) {
756 this->lineTo(reduction);
757 SkStrokerPriv::JoinProc saveJoiner = fJoiner;
758 fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
759 this->lineTo(pt2);
760 fJoiner = saveJoiner;
761 return;
762 }
763 SkASSERT(kQuad_ReductionType == reductionType);
764 SkVector normalAB, unitAB, normalBC, unitBC;
765 if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
766 this->lineTo(pt2);
767 return;
768 }
769 SkQuadConstruct quadPts;
770 this->init(kOuter_StrokeType, &quadPts, 0, 1);
771 (void) this->conicStroke(conic, &quadPts);
772 this->init(kInner_StrokeType, &quadPts, 0, 1);
773 (void) this->conicStroke(conic, &quadPts);
774 this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC);
775 this->postJoinTo(pt2, normalBC, unitBC);
776 }
777
quadTo(const SkPoint & pt1,const SkPoint & pt2)778 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
779 const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
780 SkPoint reduction;
781 ReductionType reductionType = CheckQuadLinear(quad, &reduction);
782 if (kPoint_ReductionType == reductionType) {
783 /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
784 as if it were followed by a zero-length line. Lines without length
785 can have square and round end caps. */
786 this->lineTo(pt2);
787 return;
788 }
789 if (kLine_ReductionType == reductionType) {
790 this->lineTo(pt2);
791 return;
792 }
793 if (kDegenerate_ReductionType == reductionType) {
794 this->lineTo(reduction);
795 SkStrokerPriv::JoinProc saveJoiner = fJoiner;
796 fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
797 this->lineTo(pt2);
798 fJoiner = saveJoiner;
799 return;
800 }
801 SkASSERT(kQuad_ReductionType == reductionType);
802 SkVector normalAB, unitAB, normalBC, unitBC;
803 if (!this->preJoinTo(pt1, &normalAB, &unitAB, false)) {
804 this->lineTo(pt2);
805 return;
806 }
807 SkQuadConstruct quadPts;
808 this->init(kOuter_StrokeType, &quadPts, 0, 1);
809 (void) this->quadStroke(quad, &quadPts);
810 this->init(kInner_StrokeType, &quadPts, 0, 1);
811 (void) this->quadStroke(quad, &quadPts);
812 this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC);
813
814 this->postJoinTo(pt2, normalBC, unitBC);
815 }
816
817 // Given a point on the curve and its derivative, scale the derivative by the radius, and
818 // compute the perpendicular point and its tangent.
setRayPts(const SkPoint & tPt,SkVector * dxy,SkPoint * onPt,SkVector * tangent) const819 void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
820 SkVector* tangent) const {
821 if (!dxy->setLength(fRadius)) {
822 dxy->set(fRadius, 0);
823 }
824 SkScalar axisFlip = SkIntToScalar(fStrokeType); // go opposite ways for outer, inner
825 onPt->fX = tPt.fX + axisFlip * dxy->fY;
826 onPt->fY = tPt.fY - axisFlip * dxy->fX;
827 if (tangent) {
828 *tangent = *dxy;
829 }
830 }
831
832 // Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
833 // Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
conicPerpRay(const SkConic & conic,SkScalar t,SkPoint * tPt,SkPoint * onPt,SkVector * tangent) const834 void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
835 SkVector* tangent) const {
836 SkVector dxy;
837 conic.evalAt(t, tPt, &dxy);
838 if (dxy.isZero()) {
839 dxy = conic.fPts[2] - conic.fPts[0];
840 }
841 this->setRayPts(*tPt, &dxy, onPt, tangent);
842 }
843
844 // Given a conic and a t range, find the start and end if they haven't been found already.
conicQuadEnds(const SkConic & conic,SkQuadConstruct * quadPts) const845 void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const {
846 if (!quadPts->fStartSet) {
847 SkPoint conicStartPt;
848 this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
849 &quadPts->fTangentStart);
850 quadPts->fStartSet = true;
851 }
852 if (!quadPts->fEndSet) {
853 SkPoint conicEndPt;
854 this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
855 &quadPts->fTangentEnd);
856 quadPts->fEndSet = true;
857 }
858 }
859
860
861 // Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
cubicPerpRay(const SkPoint cubic[4],SkScalar t,SkPoint * tPt,SkPoint * onPt,SkVector * tangent) const862 void SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
863 SkVector* tangent) const {
864 SkVector dxy;
865 SkPoint chopped[7];
866 SkEvalCubicAt(cubic, t, tPt, &dxy, nullptr);
867 if (dxy.isZero()) {
868 const SkPoint* cPts = cubic;
869 if (SkScalarNearlyZero(t)) {
870 dxy = cubic[2] - cubic[0];
871 } else if (SkScalarNearlyZero(1 - t)) {
872 dxy = cubic[3] - cubic[1];
873 } else {
874 // If the cubic inflection falls on the cusp, subdivide the cubic
875 // to find the tangent at that point.
876 SkChopCubicAt(cubic, chopped, t);
877 dxy = chopped[3] - chopped[2];
878 if (dxy.isZero()) {
879 dxy = chopped[3] - chopped[1];
880 cPts = chopped;
881 }
882 }
883 if (dxy.isZero()) {
884 dxy = cPts[3] - cPts[0];
885 }
886 }
887 setRayPts(*tPt, &dxy, onPt, tangent);
888 }
889
890 // Given a cubic and a t range, find the start and end if they haven't been found already.
cubicQuadEnds(const SkPoint cubic[4],SkQuadConstruct * quadPts)891 void SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
892 if (!quadPts->fStartSet) {
893 SkPoint cubicStartPt;
894 this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0],
895 &quadPts->fTangentStart);
896 quadPts->fStartSet = true;
897 }
898 if (!quadPts->fEndSet) {
899 SkPoint cubicEndPt;
900 this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2],
901 &quadPts->fTangentEnd);
902 quadPts->fEndSet = true;
903 }
904 }
905
cubicQuadMid(const SkPoint cubic[4],const SkQuadConstruct * quadPts,SkPoint * mid) const906 void SkPathStroker::cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* quadPts,
907 SkPoint* mid) const {
908 SkPoint cubicMidPt;
909 this->cubicPerpRay(cubic, quadPts->fMidT, &cubicMidPt, mid, nullptr);
910 }
911
912 // Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent.
quadPerpRay(const SkPoint quad[3],SkScalar t,SkPoint * tPt,SkPoint * onPt,SkVector * tangent) const913 void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
914 SkVector* tangent) const {
915 SkVector dxy;
916 SkEvalQuadAt(quad, t, tPt, &dxy);
917 if (dxy.isZero()) {
918 dxy = quad[2] - quad[0];
919 }
920 setRayPts(*tPt, &dxy, onPt, tangent);
921 }
922
923 // Find the intersection of the stroke tangents to construct a stroke quad.
924 // Return whether the stroke is a degenerate (a line), a quad, or must be split.
925 // Optionally compute the quad's control point.
intersectRay(SkQuadConstruct * quadPts,IntersectRayType intersectRayType STROKER_DEBUG_PARAMS (int depth)) const926 SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
927 IntersectRayType intersectRayType STROKER_DEBUG_PARAMS(int depth)) const {
928 const SkPoint& start = quadPts->fQuad[0];
929 const SkPoint& end = quadPts->fQuad[2];
930 SkVector aLen = quadPts->fTangentStart;
931 SkVector bLen = quadPts->fTangentEnd;
932 /* Slopes match when denom goes to zero:
933 axLen / ayLen == bxLen / byLen
934 (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
935 byLen * axLen == ayLen * bxLen
936 byLen * axLen - ayLen * bxLen ( == denom )
937 */
938 SkScalar denom = aLen.cross(bLen);
939 if (denom == 0 || !SkIsFinite(denom)) {
940 quadPts->fOppositeTangents = aLen.dot(bLen) < 0;
941 return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0");
942 }
943 quadPts->fOppositeTangents = false;
944 SkVector ab0 = start - end;
945 SkScalar numerA = bLen.cross(ab0);
946 SkScalar numerB = aLen.cross(ab0);
947 if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends
948 // if the perpendicular distances from the quad points to the opposite tangent line
949 // are small, a straight line is good enough
950 SkScalar dist1 = pt_to_tangent_line(start, end, quadPts->fTangentEnd);
951 SkScalar dist2 = pt_to_tangent_line(end, start, quadPts->fTangentStart);
952 if (std::max(dist1, dist2) <= fInvResScaleSquared) {
953 return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
954 "std::max(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2);
955 }
956 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
957 "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
958 }
959 // check to see if the denominator is teeny relative to the numerator
960 // if the offset by one will be lost, the ratio is too large
961 numerA /= denom;
962 bool validDivide = numerA > numerA - 1;
963 if (validDivide) {
964 if (kCtrlPt_RayType == intersectRayType) {
965 SkPoint* ctrlPt = &quadPts->fQuad[1];
966 // the intersection of the tangents need not be on the tangent segment
967 // so 0 <= numerA <= 1 is not necessarily true
968 *ctrlPt = start + quadPts->fTangentStart * numerA;
969 }
970 return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
971 "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
972 }
973 quadPts->fOppositeTangents = aLen.dot(bLen) < 0;
974 // if the lines are parallel, straight line is good enough
975 return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
976 "SkScalarNearlyZero(denom=%g)", denom);
977 }
978
979 // Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
tangentsMeet(const SkPoint cubic[4],SkQuadConstruct * quadPts)980 SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
981 SkQuadConstruct* quadPts) {
982 this->cubicQuadEnds(cubic, quadPts);
983 return this->intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth));
984 }
985
986 // Intersect the line with the quad and return the t values on the quad where the line crosses.
intersect_quad_ray(const SkPoint line[2],const SkPoint quad[3],SkScalar roots[2])987 static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) {
988 SkVector vec = line[1] - line[0];
989 SkScalar r[3];
990 for (int n = 0; n < 3; ++n) {
991 r[n] = vec.cross(quad[n] - line[0]);
992 }
993 SkScalar A = r[2];
994 SkScalar B = r[1];
995 SkScalar C = r[0];
996 A += C - 2 * B; // A = a - 2*b + c
997 B -= C; // B = -(b - c)
998 return SkFindUnitQuadRoots(A, 2 * B, C, roots);
999 }
1000
1001 // Return true if the point is close to the bounds of the quad. This is used as a quick reject.
ptInQuadBounds(const SkPoint quad[3],const SkPoint & pt) const1002 bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
1003 SkScalar xMin = std::min({quad[0].fX, quad[1].fX, quad[2].fX});
1004 if (pt.fX + fInvResScale < xMin) {
1005 return false;
1006 }
1007 SkScalar xMax = std::max({quad[0].fX, quad[1].fX, quad[2].fX});
1008 if (pt.fX - fInvResScale > xMax) {
1009 return false;
1010 }
1011 SkScalar yMin = std::min({quad[0].fY, quad[1].fY, quad[2].fY});
1012 if (pt.fY + fInvResScale < yMin) {
1013 return false;
1014 }
1015 SkScalar yMax = std::max({quad[0].fY, quad[1].fY, quad[2].fY});
1016 if (pt.fY - fInvResScale > yMax) {
1017 return false;
1018 }
1019 return true;
1020 }
1021
points_within_dist(const SkPoint & nearPt,const SkPoint & farPt,SkScalar limit)1022 static bool points_within_dist(const SkPoint& nearPt, const SkPoint& farPt, SkScalar limit) {
1023 return SkPointPriv::DistanceToSqd(nearPt, farPt) <= limit * limit;
1024 }
1025
sharp_angle(const SkPoint quad[3])1026 static bool sharp_angle(const SkPoint quad[3]) {
1027 SkVector smaller = quad[1] - quad[0];
1028 SkVector larger = quad[1] - quad[2];
1029 SkScalar smallerLen = SkPointPriv::LengthSqd(smaller);
1030 SkScalar largerLen = SkPointPriv::LengthSqd(larger);
1031 if (smallerLen > largerLen) {
1032 using std::swap;
1033 swap(smaller, larger);
1034 largerLen = smallerLen;
1035 }
1036 if (!smaller.setLength(largerLen)) {
1037 return false;
1038 }
1039 SkScalar dot = smaller.dot(larger);
1040 return dot > 0;
1041 }
1042
strokeCloseEnough(const SkPoint stroke[3],const SkPoint ray[2],SkQuadConstruct * quadPts STROKER_DEBUG_PARAMS (int depth)) const1043 SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3],
1044 const SkPoint ray[2], SkQuadConstruct* quadPts STROKER_DEBUG_PARAMS(int depth)) const {
1045 SkPoint strokeMid = SkEvalQuadAt(stroke, SK_ScalarHalf);
1046 // measure the distance from the curve to the quad-stroke midpoint, compare to radius
1047 if (points_within_dist(ray[0], strokeMid, fInvResScale)) { // if the difference is small
1048 if (sharp_angle(quadPts->fQuad)) {
1049 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1050 "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
1051 quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
1052 quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
1053 quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
1054 }
1055 return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
1056 "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScale=%g)",
1057 ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScale);
1058 }
1059 // measure the distance to quad's bounds (quick reject)
1060 // an alternative : look for point in triangle
1061 if (!ptInQuadBounds(stroke, ray[0])) { // if far, subdivide
1062 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1063 "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)",
1064 stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY,
1065 ray[0].fX, ray[0].fY);
1066 }
1067 // measure the curve ray distance to the quad-stroke
1068 SkScalar roots[2];
1069 int rootCount = intersect_quad_ray(ray, stroke, roots);
1070 if (rootCount != 1) {
1071 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1072 "rootCount=%d != 1", rootCount);
1073 }
1074 SkPoint quadPt = SkEvalQuadAt(stroke, roots[0]);
1075 SkScalar error = fInvResScale * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2);
1076 if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done
1077 if (sharp_angle(quadPts->fQuad)) {
1078 return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
1079 "sharp_angle (2) =%g,%g, %g,%g, %g,%g",
1080 quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
1081 quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
1082 quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
1083 }
1084 return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
1085 "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)",
1086 ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error);
1087 }
1088 // otherwise, subdivide
1089 return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through");
1090 }
1091
compareQuadCubic(const SkPoint cubic[4],SkQuadConstruct * quadPts)1092 SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4],
1093 SkQuadConstruct* quadPts) {
1094 // get the quadratic approximation of the stroke
1095 this->cubicQuadEnds(cubic, quadPts);
1096 ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
1097 STROKER_DEBUG_PARAMS(fRecursionDepth) );
1098 if (resultType != kQuad_ResultType) {
1099 return resultType;
1100 }
1101 // project a ray from the curve to the stroke
1102 SkPoint ray[2]; // points near midpoint on quad, midpoint on cubic
1103 this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], nullptr);
1104 return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
1105 STROKER_DEBUG_PARAMS(fRecursionDepth));
1106 }
1107
compareQuadConic(const SkConic & conic,SkQuadConstruct * quadPts) const1108 SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic,
1109 SkQuadConstruct* quadPts) const {
1110 // get the quadratic approximation of the stroke
1111 this->conicQuadEnds(conic, quadPts);
1112 ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
1113 STROKER_DEBUG_PARAMS(fRecursionDepth) );
1114 if (resultType != kQuad_ResultType) {
1115 return resultType;
1116 }
1117 // project a ray from the curve to the stroke
1118 SkPoint ray[2]; // points near midpoint on quad, midpoint on conic
1119 this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], nullptr);
1120 return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
1121 STROKER_DEBUG_PARAMS(fRecursionDepth));
1122 }
1123
compareQuadQuad(const SkPoint quad[3],SkQuadConstruct * quadPts)1124 SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
1125 SkQuadConstruct* quadPts) {
1126 // get the quadratic approximation of the stroke
1127 if (!quadPts->fStartSet) {
1128 SkPoint quadStartPt;
1129 this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0],
1130 &quadPts->fTangentStart);
1131 quadPts->fStartSet = true;
1132 }
1133 if (!quadPts->fEndSet) {
1134 SkPoint quadEndPt;
1135 this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2],
1136 &quadPts->fTangentEnd);
1137 quadPts->fEndSet = true;
1138 }
1139 ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
1140 STROKER_DEBUG_PARAMS(fRecursionDepth));
1141 if (resultType != kQuad_ResultType) {
1142 return resultType;
1143 }
1144 // project a ray from the curve to the stroke
1145 SkPoint ray[2];
1146 this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], nullptr);
1147 return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
1148 STROKER_DEBUG_PARAMS(fRecursionDepth));
1149 }
1150
addDegenerateLine(const SkQuadConstruct * quadPts)1151 void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) {
1152 const SkPoint* quad = quadPts->fQuad;
1153 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1154 path->lineTo(quad[2]);
1155 }
1156
cubicMidOnLine(const SkPoint cubic[4],const SkQuadConstruct * quadPts) const1157 bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* quadPts) const {
1158 SkPoint strokeMid;
1159 this->cubicQuadMid(cubic, quadPts, &strokeMid);
1160 SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]);
1161 return dist < fInvResScaleSquared;
1162 }
1163
cubicStroke(const SkPoint cubic[4],SkQuadConstruct * quadPts)1164 bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
1165 if (!fFoundTangents) {
1166 ResultType resultType = this->tangentsMeet(cubic, quadPts);
1167 if (kQuad_ResultType != resultType) {
1168 if ((kDegenerate_ResultType == resultType
1169 || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2],
1170 fInvResScale)) && cubicMidOnLine(cubic, quadPts)) {
1171 addDegenerateLine(quadPts);
1172 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1173 return true;
1174 }
1175 } else {
1176 fFoundTangents = true;
1177 }
1178 }
1179 if (fFoundTangents) {
1180 ResultType resultType = this->compareQuadCubic(cubic, quadPts);
1181 if (kQuad_ResultType == resultType) {
1182 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1183 const SkPoint* stroke = quadPts->fQuad;
1184 path->quadTo(stroke[1], stroke[2]);
1185 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1186 return true;
1187 }
1188 if (kDegenerate_ResultType == resultType) {
1189 if (!quadPts->fOppositeTangents) {
1190 addDegenerateLine(quadPts);
1191 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1192 return true;
1193 }
1194 }
1195 }
1196 if (!quadPts->fQuad[2].isFinite()) {
1197 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1198 return false; // just abort if projected quad isn't representable
1199 }
1200 #if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
1201 SkDEBUGCODE(gMaxRecursion[fFoundTangents] = std::max(gMaxRecursion[fFoundTangents],
1202 fRecursionDepth + 1));
1203 #endif
1204 if (++fRecursionDepth > kRecursiveLimits[fFoundTangents]) {
1205 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1206 // If we stop making progress, just emit a line and move on
1207 addDegenerateLine(quadPts);
1208 return true;
1209 }
1210 SkQuadConstruct half;
1211 if (!half.initWithStart(quadPts)) {
1212 addDegenerateLine(quadPts);
1213 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1214 --fRecursionDepth;
1215 return true;
1216 }
1217 if (!this->cubicStroke(cubic, &half)) {
1218 return false;
1219 }
1220 if (!half.initWithEnd(quadPts)) {
1221 addDegenerateLine(quadPts);
1222 DEBUG_CUBIC_RECURSION_TRACK_DEPTH(fRecursionDepth);
1223 --fRecursionDepth;
1224 return true;
1225 }
1226 if (!this->cubicStroke(cubic, &half)) {
1227 return false;
1228 }
1229 --fRecursionDepth;
1230 return true;
1231 }
1232
conicStroke(const SkConic & conic,SkQuadConstruct * quadPts)1233 bool SkPathStroker::conicStroke(const SkConic& conic, SkQuadConstruct* quadPts) {
1234 ResultType resultType = this->compareQuadConic(conic, quadPts);
1235 if (kQuad_ResultType == resultType) {
1236 const SkPoint* stroke = quadPts->fQuad;
1237 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1238 path->quadTo(stroke[1], stroke[2]);
1239 return true;
1240 }
1241 if (kDegenerate_ResultType == resultType) {
1242 addDegenerateLine(quadPts);
1243 return true;
1244 }
1245 #if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
1246 SkDEBUGCODE(gMaxRecursion[kConic_RecursiveLimit] = std::max(gMaxRecursion[kConic_RecursiveLimit],
1247 fRecursionDepth + 1));
1248 #endif
1249 if (++fRecursionDepth > kRecursiveLimits[kConic_RecursiveLimit]) {
1250 // If we stop making progress, just emit a line and move on
1251 addDegenerateLine(quadPts);
1252 return true;
1253 }
1254 SkQuadConstruct half;
1255 (void) half.initWithStart(quadPts);
1256 if (!this->conicStroke(conic, &half)) {
1257 return false;
1258 }
1259 (void) half.initWithEnd(quadPts);
1260 if (!this->conicStroke(conic, &half)) {
1261 return false;
1262 }
1263 --fRecursionDepth;
1264 return true;
1265 }
1266
quadStroke(const SkPoint quad[3],SkQuadConstruct * quadPts)1267 bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) {
1268 ResultType resultType = this->compareQuadQuad(quad, quadPts);
1269 if (kQuad_ResultType == resultType) {
1270 const SkPoint* stroke = quadPts->fQuad;
1271 SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
1272 path->quadTo(stroke[1], stroke[2]);
1273 return true;
1274 }
1275 if (kDegenerate_ResultType == resultType) {
1276 addDegenerateLine(quadPts);
1277 return true;
1278 }
1279 #if QUAD_STROKE_APPROX_EXTENDED_DEBUGGING
1280 SkDEBUGCODE(gMaxRecursion[kQuad_RecursiveLimit] = std::max(gMaxRecursion[kQuad_RecursiveLimit],
1281 fRecursionDepth + 1));
1282 #endif
1283 if (++fRecursionDepth > kRecursiveLimits[kQuad_RecursiveLimit]) {
1284 // If we stop making progress, just emit a line and move on
1285 addDegenerateLine(quadPts);
1286 return true;
1287 }
1288 SkQuadConstruct half;
1289 (void) half.initWithStart(quadPts);
1290 if (!this->quadStroke(quad, &half)) {
1291 return false;
1292 }
1293 (void) half.initWithEnd(quadPts);
1294 if (!this->quadStroke(quad, &half)) {
1295 return false;
1296 }
1297 --fRecursionDepth;
1298 return true;
1299 }
1300
cubicTo(const SkPoint & pt1,const SkPoint & pt2,const SkPoint & pt3)1301 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
1302 const SkPoint& pt3) {
1303 const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
1304 SkPoint reduction[3];
1305 const SkPoint* tangentPt;
1306 ReductionType reductionType = CheckCubicLinear(cubic, reduction, &tangentPt);
1307 if (kPoint_ReductionType == reductionType) {
1308 /* If the stroke consists of a moveTo followed by a degenerate curve, treat it
1309 as if it were followed by a zero-length line. Lines without length
1310 can have square and round end caps. */
1311 this->lineTo(pt3);
1312 return;
1313 }
1314 if (kLine_ReductionType == reductionType) {
1315 this->lineTo(pt3);
1316 return;
1317 }
1318 if (kDegenerate_ReductionType <= reductionType && kDegenerate3_ReductionType >= reductionType) {
1319 this->lineTo(reduction[0]);
1320 SkStrokerPriv::JoinProc saveJoiner = fJoiner;
1321 fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
1322 if (kDegenerate2_ReductionType <= reductionType) {
1323 this->lineTo(reduction[1]);
1324 }
1325 if (kDegenerate3_ReductionType == reductionType) {
1326 this->lineTo(reduction[2]);
1327 }
1328 this->lineTo(pt3);
1329 fJoiner = saveJoiner;
1330 return;
1331 }
1332 SkASSERT(kQuad_ReductionType == reductionType);
1333 SkVector normalAB, unitAB, normalCD, unitCD;
1334 if (!this->preJoinTo(*tangentPt, &normalAB, &unitAB, false)) {
1335 this->lineTo(pt3);
1336 return;
1337 }
1338 SkScalar tValues[2];
1339 int count = SkFindCubicInflections(cubic, tValues);
1340 SkScalar lastT = 0;
1341 for (int index = 0; index <= count; ++index) {
1342 SkScalar nextT = index < count ? tValues[index] : 1;
1343 SkQuadConstruct quadPts;
1344 this->init(kOuter_StrokeType, &quadPts, lastT, nextT);
1345 (void) this->cubicStroke(cubic, &quadPts);
1346 this->init(kInner_StrokeType, &quadPts, lastT, nextT);
1347 (void) this->cubicStroke(cubic, &quadPts);
1348 lastT = nextT;
1349 }
1350 SkScalar cusp = SkFindCubicCusp(cubic);
1351 if (cusp > 0) {
1352 SkPoint cuspLoc;
1353 SkEvalCubicAt(cubic, cusp, &cuspLoc, nullptr, nullptr);
1354 fCusper.addCircle(cuspLoc.fX, cuspLoc.fY, fRadius);
1355 }
1356 // emit the join even if one stroke succeeded but the last one failed
1357 // this avoids reversing an inner stroke with a partial path followed by another moveto
1358 this->setCubicEndNormal(cubic, normalAB, unitAB, &normalCD, &unitCD);
1359
1360 this->postJoinTo(pt3, normalCD, unitCD);
1361 }
1362
1363 ///////////////////////////////////////////////////////////////////////////////
1364 ///////////////////////////////////////////////////////////////////////////////
1365
1366 #include "src/core/SkPaintDefaults.h"
1367
SkStroke()1368 SkStroke::SkStroke() {
1369 fWidth = SK_Scalar1;
1370 fMiterLimit = SkPaintDefaults_MiterLimit;
1371 fResScale = 1;
1372 fCap = SkPaint::kDefault_Cap;
1373 fJoin = SkPaint::kDefault_Join;
1374 fDoFill = false;
1375 }
1376
SkStroke(const SkPaint & p)1377 SkStroke::SkStroke(const SkPaint& p) {
1378 fWidth = p.getStrokeWidth();
1379 fMiterLimit = p.getStrokeMiter();
1380 fResScale = 1;
1381 fCap = (uint8_t)p.getStrokeCap();
1382 fJoin = (uint8_t)p.getStrokeJoin();
1383 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
1384 }
1385
SkStroke(const SkPaint & p,SkScalar width)1386 SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
1387 fWidth = width;
1388 fMiterLimit = p.getStrokeMiter();
1389 fResScale = 1;
1390 fCap = (uint8_t)p.getStrokeCap();
1391 fJoin = (uint8_t)p.getStrokeJoin();
1392 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
1393 }
1394
setWidth(SkScalar width)1395 void SkStroke::setWidth(SkScalar width) {
1396 SkASSERT(width >= 0);
1397 fWidth = width;
1398 }
1399
setMiterLimit(SkScalar miterLimit)1400 void SkStroke::setMiterLimit(SkScalar miterLimit) {
1401 SkASSERT(miterLimit >= 0);
1402 fMiterLimit = miterLimit;
1403 }
1404
setCap(SkPaint::Cap cap)1405 void SkStroke::setCap(SkPaint::Cap cap) {
1406 SkASSERT((unsigned)cap < SkPaint::kCapCount);
1407 fCap = SkToU8(cap);
1408 }
1409
setJoin(SkPaint::Join join)1410 void SkStroke::setJoin(SkPaint::Join join) {
1411 SkASSERT((unsigned)join < SkPaint::kJoinCount);
1412 fJoin = SkToU8(join);
1413 }
1414
1415 ///////////////////////////////////////////////////////////////////////////////
1416
1417 // If src==dst, then we use a tmp path to record the stroke, and then swap
1418 // its contents with src when we're done.
1419 class AutoTmpPath {
1420 public:
AutoTmpPath(const SkPath & src,SkPath ** dst)1421 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
1422 if (&src == *dst) {
1423 *dst = &fTmpDst;
1424 fSwapWithSrc = true;
1425 } else {
1426 (*dst)->reset();
1427 fSwapWithSrc = false;
1428 }
1429 }
1430
~AutoTmpPath()1431 ~AutoTmpPath() {
1432 if (fSwapWithSrc) {
1433 fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
1434 }
1435 }
1436
1437 private:
1438 SkPath fTmpDst;
1439 const SkPath& fSrc;
1440 bool fSwapWithSrc;
1441 };
1442
strokePath(const SkPath & src,SkPath * dst) const1443 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
1444 SkASSERT(dst);
1445
1446 SkScalar radius = SkScalarHalf(fWidth);
1447
1448 AutoTmpPath tmp(src, &dst);
1449
1450 if (radius <= 0) {
1451 return;
1452 }
1453
1454 // If src is really a rect, call our specialty strokeRect() method
1455 {
1456 SkRect rect;
1457 bool isClosed = false;
1458 SkPathDirection dir;
1459 if (src.isRect(&rect, &isClosed, &dir) && isClosed) {
1460 this->strokeRect(rect, dst, dir);
1461 // our answer should preserve the inverseness of the src
1462 if (src.isInverseFillType()) {
1463 SkASSERT(!dst->isInverseFillType());
1464 dst->toggleInverseFillType();
1465 }
1466 return;
1467 }
1468 }
1469
1470 // We can always ignore centers for stroke and fill convex line-only paths
1471 // TODO: remove the line-only restriction
1472 bool ignoreCenter = fDoFill && (src.getSegmentMasks() == SkPath::kLine_SegmentMask) &&
1473 src.isLastContourClosed() && src.isConvex();
1474
1475 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(),
1476 fResScale, ignoreCenter);
1477 SkPath::Iter iter(src, false);
1478 SkPath::Verb lastSegment = SkPath::kMove_Verb;
1479
1480 for (;;) {
1481 SkPoint pts[4];
1482 switch (iter.next(pts)) {
1483 case SkPath::kMove_Verb:
1484 stroker.moveTo(pts[0]);
1485 break;
1486 case SkPath::kLine_Verb:
1487 stroker.lineTo(pts[1], &iter);
1488 lastSegment = SkPath::kLine_Verb;
1489 break;
1490 case SkPath::kQuad_Verb:
1491 stroker.quadTo(pts[1], pts[2]);
1492 lastSegment = SkPath::kQuad_Verb;
1493 break;
1494 case SkPath::kConic_Verb: {
1495 stroker.conicTo(pts[1], pts[2], iter.conicWeight());
1496 lastSegment = SkPath::kConic_Verb;
1497 } break;
1498 case SkPath::kCubic_Verb:
1499 stroker.cubicTo(pts[1], pts[2], pts[3]);
1500 lastSegment = SkPath::kCubic_Verb;
1501 break;
1502 case SkPath::kClose_Verb:
1503 if (SkPaint::kButt_Cap != this->getCap()) {
1504 /* If the stroke consists of a moveTo followed by a close, treat it
1505 as if it were followed by a zero-length line. Lines without length
1506 can have square and round end caps. */
1507 if (stroker.hasOnlyMoveTo()) {
1508 stroker.lineTo(stroker.moveToPt());
1509 goto ZERO_LENGTH;
1510 }
1511 /* If the stroke consists of a moveTo followed by one or more zero-length
1512 verbs, then followed by a close, treat is as if it were followed by a
1513 zero-length line. Lines without length can have square & round end caps. */
1514 if (stroker.isCurrentContourEmpty()) {
1515 ZERO_LENGTH:
1516 lastSegment = SkPath::kLine_Verb;
1517 break;
1518 }
1519 }
1520 stroker.close(lastSegment == SkPath::kLine_Verb);
1521 break;
1522 case SkPath::kDone_Verb:
1523 goto DONE;
1524 }
1525 }
1526 DONE:
1527 stroker.done(dst, lastSegment == SkPath::kLine_Verb);
1528
1529 if (fDoFill && !ignoreCenter) {
1530 if (SkPathPriv::ComputeFirstDirection(src) == SkPathFirstDirection::kCCW) {
1531 dst->reverseAddPath(src);
1532 } else {
1533 dst->addPath(src);
1534 }
1535 } else {
1536 // Seems like we can assume that a 2-point src would always result in
1537 // a convex stroke, but testing has proved otherwise.
1538 // TODO: fix the stroker to make this assumption true (without making
1539 // it slower that the work that will be done in computeConvexity())
1540 #if 0
1541 // this test results in a non-convex stroke :(
1542 static void test(SkCanvas* canvas) {
1543 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 };
1544 SkPaint paint;
1545 paint.setStrokeWidth(7);
1546 paint.setStrokeCap(SkPaint::kRound_Cap);
1547 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
1548 }
1549 #endif
1550 #if 0
1551 if (2 == src.countPoints()) {
1552 dst->setIsConvex(true);
1553 }
1554 #endif
1555 }
1556
1557 // our answer should preserve the inverseness of the src
1558 if (src.isInverseFillType()) {
1559 SkASSERT(!dst->isInverseFillType());
1560 dst->toggleInverseFillType();
1561 }
1562 }
1563
reverse_direction(SkPathDirection dir)1564 static SkPathDirection reverse_direction(SkPathDirection dir) {
1565 static const SkPathDirection gOpposite[] = { SkPathDirection::kCCW, SkPathDirection::kCW };
1566 return gOpposite[(int)dir];
1567 }
1568
addBevel(SkPath * path,const SkRect & r,const SkRect & outer,SkPathDirection dir)1569 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPathDirection dir) {
1570 SkPoint pts[8];
1571
1572 if (SkPathDirection::kCW == dir) {
1573 pts[0].set(r.fLeft, outer.fTop);
1574 pts[1].set(r.fRight, outer.fTop);
1575 pts[2].set(outer.fRight, r.fTop);
1576 pts[3].set(outer.fRight, r.fBottom);
1577 pts[4].set(r.fRight, outer.fBottom);
1578 pts[5].set(r.fLeft, outer.fBottom);
1579 pts[6].set(outer.fLeft, r.fBottom);
1580 pts[7].set(outer.fLeft, r.fTop);
1581 } else {
1582 pts[7].set(r.fLeft, outer.fTop);
1583 pts[6].set(r.fRight, outer.fTop);
1584 pts[5].set(outer.fRight, r.fTop);
1585 pts[4].set(outer.fRight, r.fBottom);
1586 pts[3].set(r.fRight, outer.fBottom);
1587 pts[2].set(r.fLeft, outer.fBottom);
1588 pts[1].set(outer.fLeft, r.fBottom);
1589 pts[0].set(outer.fLeft, r.fTop);
1590 }
1591 path->addPoly(pts, 8, true);
1592 }
1593
strokeRect(const SkRect & origRect,SkPath * dst,SkPathDirection dir) const1594 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
1595 SkPathDirection dir) const {
1596 SkASSERT(dst != nullptr);
1597 dst->reset();
1598
1599 SkScalar radius = SkScalarHalf(fWidth);
1600 if (radius <= 0) {
1601 return;
1602 }
1603
1604 SkScalar rw = origRect.width();
1605 SkScalar rh = origRect.height();
1606 if ((rw < 0) ^ (rh < 0)) {
1607 dir = reverse_direction(dir);
1608 }
1609 SkRect rect(origRect);
1610 rect.sort();
1611 // reassign these, now that we know they'll be >= 0
1612 rw = rect.width();
1613 rh = rect.height();
1614
1615 SkRect r(rect);
1616 r.outset(radius, radius);
1617
1618 SkPaint::Join join = (SkPaint::Join)fJoin;
1619 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
1620 join = SkPaint::kBevel_Join;
1621 }
1622
1623 switch (join) {
1624 case SkPaint::kMiter_Join:
1625 dst->addRect(r, dir);
1626 break;
1627 case SkPaint::kBevel_Join:
1628 addBevel(dst, rect, r, dir);
1629 break;
1630 case SkPaint::kRound_Join:
1631 dst->addRoundRect(r, radius, radius, dir);
1632 break;
1633 default:
1634 break;
1635 }
1636
1637 if (fWidth < std::min(rw, rh) && !fDoFill) {
1638 r = rect;
1639 r.inset(radius, radius);
1640 dst->addRect(r, reverse_direction(dir));
1641 }
1642 }
1643