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