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