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