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