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