1 /* 2 * Copyright 2008 The Android Open Source Project 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkStrokerPriv.h" 9 #include "SkGeometry.h" 10 #include "SkPath.h" 11 12 #define kMaxQuadSubdivide 5 13 #define kMaxCubicSubdivide 7 14 15 static inline bool degenerate_vector(const SkVector& v) { 16 return !SkPoint::CanNormalize(v.fX, v.fY); 17 } 18 19 static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) { 20 /* root2/2 is a 45-degree angle 21 make this constant bigger for more subdivisions (but not >= 1) 22 */ 23 static const SkScalar kFlatEnoughNormalDotProd = 24 SK_ScalarSqrt2/2 + SK_Scalar1/10; 25 26 SkASSERT(kFlatEnoughNormalDotProd > 0 && 27 kFlatEnoughNormalDotProd < SK_Scalar1); 28 29 return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd; 30 } 31 32 static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) { 33 // if the dot-product is -1, then we are definitely too pinchy. We tweak 34 // that by an epsilon to ensure we have significant bits in our test 35 static const int kMinSigBitsForDot = 8; 36 static const SkScalar kDotEpsilon = FLT_EPSILON * (1 << kMinSigBitsForDot); 37 static const SkScalar kTooPinchyNormalDotProd = kDotEpsilon - 1; 38 39 // just some sanity asserts to help document the expected range 40 SkASSERT(kTooPinchyNormalDotProd >= -1); 41 SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999)); 42 43 SkScalar dot = SkPoint::DotProduct(norm0, norm1); 44 return dot <= kTooPinchyNormalDotProd; 45 } 46 47 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, 48 SkScalar radius, 49 SkVector* normal, SkVector* unitNormal) { 50 if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) { 51 return false; 52 } 53 unitNormal->rotateCCW(); 54 unitNormal->scale(radius, normal); 55 return true; 56 } 57 58 static bool set_normal_unitnormal(const SkVector& vec, 59 SkScalar radius, 60 SkVector* normal, SkVector* unitNormal) { 61 if (!unitNormal->setNormalize(vec.fX, vec.fY)) { 62 return false; 63 } 64 unitNormal->rotateCCW(); 65 unitNormal->scale(radius, normal); 66 return true; 67 } 68 69 /////////////////////////////////////////////////////////////////////////////// 70 71 class SkPathStroker { 72 public: 73 SkPathStroker(const SkPath& src, 74 SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap, 75 SkPaint::Join join); 76 77 void moveTo(const SkPoint&); 78 void lineTo(const SkPoint&); 79 void quadTo(const SkPoint&, const SkPoint&); 80 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); 81 void close(bool isLine) { this->finishContour(true, isLine); } 82 83 void done(SkPath* dst, bool isLine) { 84 this->finishContour(false, isLine); 85 fOuter.addPath(fExtra); 86 dst->swap(fOuter); 87 } 88 89 private: 90 SkScalar fRadius; 91 SkScalar fInvMiterLimit; 92 93 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; 94 SkPoint fFirstPt, fPrevPt; // on original path 95 SkPoint fFirstOuterPt; 96 int fSegmentCount; 97 bool fPrevIsLine; 98 99 SkStrokerPriv::CapProc fCapper; 100 SkStrokerPriv::JoinProc fJoiner; 101 102 SkPath fInner, fOuter; // outer is our working answer, inner is temp 103 SkPath fExtra; // added as extra complete contours 104 105 void finishContour(bool close, bool isLine); 106 void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal, 107 bool isLine); 108 void postJoinTo(const SkPoint&, const SkVector& normal, 109 const SkVector& unitNormal); 110 111 void line_to(const SkPoint& currPt, const SkVector& normal); 112 void quad_to(const SkPoint pts[3], 113 const SkVector& normalAB, const SkVector& unitNormalAB, 114 SkVector* normalBC, SkVector* unitNormalBC, 115 int subDivide); 116 void cubic_to(const SkPoint pts[4], 117 const SkVector& normalAB, const SkVector& unitNormalAB, 118 SkVector* normalCD, SkVector* unitNormalCD, 119 int subDivide); 120 }; 121 122 /////////////////////////////////////////////////////////////////////////////// 123 124 void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal, 125 SkVector* unitNormal, bool currIsLine) { 126 SkASSERT(fSegmentCount >= 0); 127 128 SkScalar prevX = fPrevPt.fX; 129 SkScalar prevY = fPrevPt.fY; 130 131 SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal, 132 unitNormal)); 133 134 if (fSegmentCount == 0) { 135 fFirstNormal = *normal; 136 fFirstUnitNormal = *unitNormal; 137 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY); 138 139 fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY); 140 fInner.moveTo(prevX - normal->fX, prevY - normal->fY); 141 } else { // we have a previous segment 142 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal, 143 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine); 144 } 145 fPrevIsLine = currIsLine; 146 } 147 148 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal, 149 const SkVector& unitNormal) { 150 fPrevPt = currPt; 151 fPrevUnitNormal = unitNormal; 152 fPrevNormal = normal; 153 fSegmentCount += 1; 154 } 155 156 void SkPathStroker::finishContour(bool close, bool currIsLine) { 157 if (fSegmentCount > 0) { 158 SkPoint pt; 159 160 if (close) { 161 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, 162 fFirstUnitNormal, fRadius, fInvMiterLimit, 163 fPrevIsLine, currIsLine); 164 fOuter.close(); 165 // now add fInner as its own contour 166 fInner.getLastPt(&pt); 167 fOuter.moveTo(pt.fX, pt.fY); 168 fOuter.reversePathTo(fInner); 169 fOuter.close(); 170 } else { // add caps to start and end 171 // cap the end 172 fInner.getLastPt(&pt); 173 fCapper(&fOuter, fPrevPt, fPrevNormal, pt, 174 currIsLine ? &fInner : NULL); 175 fOuter.reversePathTo(fInner); 176 // cap the start 177 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt, 178 fPrevIsLine ? &fInner : NULL); 179 fOuter.close(); 180 } 181 } 182 // since we may re-use fInner, we rewind instead of reset, to save on 183 // reallocating its internal storage. 184 fInner.rewind(); 185 fSegmentCount = -1; 186 } 187 188 /////////////////////////////////////////////////////////////////////////////// 189 190 SkPathStroker::SkPathStroker(const SkPath& src, 191 SkScalar radius, SkScalar miterLimit, 192 SkPaint::Cap cap, SkPaint::Join join) 193 : fRadius(radius) { 194 195 /* This is only used when join is miter_join, but we initialize it here 196 so that it is always defined, to fis valgrind warnings. 197 */ 198 fInvMiterLimit = 0; 199 200 if (join == SkPaint::kMiter_Join) { 201 if (miterLimit <= SK_Scalar1) { 202 join = SkPaint::kBevel_Join; 203 } else { 204 fInvMiterLimit = SkScalarInvert(miterLimit); 205 } 206 } 207 fCapper = SkStrokerPriv::CapFactory(cap); 208 fJoiner = SkStrokerPriv::JoinFactory(join); 209 fSegmentCount = -1; 210 fPrevIsLine = false; 211 212 // Need some estimate of how large our final result (fOuter) 213 // and our per-contour temp (fInner) will be, so we don't spend 214 // extra time repeatedly growing these arrays. 215 // 216 // 3x for result == inner + outer + join (swag) 217 // 1x for inner == 'wag' (worst contour length would be better guess) 218 fOuter.incReserve(src.countPoints() * 3); 219 fInner.incReserve(src.countPoints()); 220 } 221 222 void SkPathStroker::moveTo(const SkPoint& pt) { 223 if (fSegmentCount > 0) { 224 this->finishContour(false, false); 225 } 226 fSegmentCount = 0; 227 fFirstPt = fPrevPt = pt; 228 } 229 230 void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) { 231 fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY); 232 fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY); 233 } 234 235 void SkPathStroker::lineTo(const SkPoint& currPt) { 236 if (SkPath::IsLineDegenerate(fPrevPt, currPt)) { 237 return; 238 } 239 SkVector normal, unitNormal; 240 241 this->preJoinTo(currPt, &normal, &unitNormal, true); 242 this->line_to(currPt, normal); 243 this->postJoinTo(currPt, normal, unitNormal); 244 } 245 246 void SkPathStroker::quad_to(const SkPoint pts[3], 247 const SkVector& normalAB, const SkVector& unitNormalAB, 248 SkVector* normalBC, SkVector* unitNormalBC, 249 int subDivide) { 250 if (!set_normal_unitnormal(pts[1], pts[2], fRadius, 251 normalBC, unitNormalBC)) { 252 // pts[1] nearly equals pts[2], so just draw a line to pts[2] 253 this->line_to(pts[2], normalAB); 254 *normalBC = normalAB; 255 *unitNormalBC = unitNormalAB; 256 return; 257 } 258 259 if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) { 260 SkPoint tmp[5]; 261 SkVector norm, unit; 262 263 SkChopQuadAtHalf(pts, tmp); 264 this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide); 265 this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide); 266 } else { 267 SkVector normalB; 268 269 normalB = pts[2] - pts[0]; 270 normalB.rotateCCW(); 271 SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC); 272 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, 273 SkScalarSqrt((SK_Scalar1 + dot)/2)))); 274 275 fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 276 pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY); 277 fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 278 pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY); 279 } 280 } 281 282 void SkPathStroker::cubic_to(const SkPoint pts[4], 283 const SkVector& normalAB, const SkVector& unitNormalAB, 284 SkVector* normalCD, SkVector* unitNormalCD, 285 int subDivide) { 286 SkVector ab = pts[1] - pts[0]; 287 SkVector cd = pts[3] - pts[2]; 288 SkVector normalBC, unitNormalBC; 289 290 bool degenerateAB = degenerate_vector(ab); 291 bool degenerateCD = degenerate_vector(cd); 292 293 if (degenerateAB && degenerateCD) { 294 DRAW_LINE: 295 this->line_to(pts[3], normalAB); 296 *normalCD = normalAB; 297 *unitNormalCD = unitNormalAB; 298 return; 299 } 300 301 if (degenerateAB) { 302 ab = pts[2] - pts[0]; 303 degenerateAB = degenerate_vector(ab); 304 } 305 if (degenerateCD) { 306 cd = pts[3] - pts[1]; 307 degenerateCD = degenerate_vector(cd); 308 } 309 if (degenerateAB || degenerateCD) { 310 goto DRAW_LINE; 311 } 312 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); 313 bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius, 314 &normalBC, &unitNormalBC); 315 #ifndef SK_IGNORE_CUBIC_STROKE_FIX 316 if (--subDivide < 0) { 317 goto DRAW_LINE; 318 } 319 #endif 320 if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) || 321 normals_too_curvy(unitNormalBC, *unitNormalCD)) { 322 #ifdef SK_IGNORE_CUBIC_STROKE_FIX 323 // subdivide if we can 324 if (--subDivide < 0) { 325 goto DRAW_LINE; 326 } 327 #endif 328 SkPoint tmp[7]; 329 SkVector norm, unit, dummy, unitDummy; 330 331 SkChopCubicAtHalf(pts, tmp); 332 this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, 333 subDivide); 334 // we use dummys since we already have a valid (and more accurate) 335 // normals for CD 336 this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide); 337 } else { 338 SkVector normalB, normalC; 339 340 // need normals to inset/outset the off-curve pts B and C 341 342 SkVector unitBC = pts[2] - pts[1]; 343 unitBC.normalize(); 344 unitBC.rotateCCW(); 345 346 normalB = unitNormalAB + unitBC; 347 normalC = *unitNormalCD + unitBC; 348 349 SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC); 350 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, 351 SkScalarSqrt((SK_Scalar1 + dot)/2)))); 352 dot = SkPoint::DotProduct(*unitNormalCD, unitBC); 353 SkAssertResult(normalC.setLength(SkScalarDiv(fRadius, 354 SkScalarSqrt((SK_Scalar1 + dot)/2)))); 355 356 fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 357 pts[2].fX + normalC.fX, pts[2].fY + normalC.fY, 358 pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY); 359 360 fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 361 pts[2].fX - normalC.fX, pts[2].fY - normalC.fY, 362 pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY); 363 } 364 } 365 366 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { 367 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); 368 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); 369 370 if (degenerateAB | degenerateBC) { 371 if (degenerateAB ^ degenerateBC) { 372 this->lineTo(pt2); 373 } 374 return; 375 } 376 377 SkVector normalAB, unitAB, normalBC, unitBC; 378 379 this->preJoinTo(pt1, &normalAB, &unitAB, false); 380 381 { 382 SkPoint pts[3], tmp[5]; 383 pts[0] = fPrevPt; 384 pts[1] = pt1; 385 pts[2] = pt2; 386 387 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) { 388 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY); 389 unitBC.rotateCCW(); 390 if (normals_too_pinchy(unitAB, unitBC)) { 391 normalBC = unitBC; 392 normalBC.scale(fRadius); 393 394 fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY); 395 fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY); 396 fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY); 397 398 fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY); 399 fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY); 400 fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY); 401 402 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius, 403 SkPath::kCW_Direction); 404 } else { 405 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC, 406 kMaxQuadSubdivide); 407 SkVector n = normalBC; 408 SkVector u = unitBC; 409 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC, 410 kMaxQuadSubdivide); 411 } 412 } else { 413 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC, 414 kMaxQuadSubdivide); 415 } 416 } 417 418 this->postJoinTo(pt2, normalBC, unitBC); 419 } 420 421 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, 422 const SkPoint& pt3) { 423 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); 424 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); 425 bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3); 426 427 if (degenerateAB + degenerateBC + degenerateCD >= 2) { 428 this->lineTo(pt3); 429 return; 430 } 431 432 SkVector normalAB, unitAB, normalCD, unitCD; 433 434 // find the first tangent (which might be pt1 or pt2 435 { 436 const SkPoint* nextPt = &pt1; 437 if (degenerateAB) 438 nextPt = &pt2; 439 this->preJoinTo(*nextPt, &normalAB, &unitAB, false); 440 } 441 442 { 443 SkPoint pts[4], tmp[13]; 444 int i, count; 445 SkVector n, u; 446 SkScalar tValues[3]; 447 448 pts[0] = fPrevPt; 449 pts[1] = pt1; 450 pts[2] = pt2; 451 pts[3] = pt3; 452 453 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); 454 n = normalAB; 455 u = unitAB; 456 for (i = 0; i < count; i++) { 457 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, 458 kMaxCubicSubdivide); 459 if (i == count - 1) { 460 break; 461 } 462 n = normalCD; 463 u = unitCD; 464 465 } 466 } 467 468 this->postJoinTo(pt3, normalCD, unitCD); 469 } 470 471 /////////////////////////////////////////////////////////////////////////////// 472 /////////////////////////////////////////////////////////////////////////////// 473 474 #include "SkPaintDefaults.h" 475 476 SkStroke::SkStroke() { 477 fWidth = SK_Scalar1; 478 fMiterLimit = SkPaintDefaults_MiterLimit; 479 fCap = SkPaint::kDefault_Cap; 480 fJoin = SkPaint::kDefault_Join; 481 fDoFill = false; 482 } 483 484 SkStroke::SkStroke(const SkPaint& p) { 485 fWidth = p.getStrokeWidth(); 486 fMiterLimit = p.getStrokeMiter(); 487 fCap = (uint8_t)p.getStrokeCap(); 488 fJoin = (uint8_t)p.getStrokeJoin(); 489 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 490 } 491 492 SkStroke::SkStroke(const SkPaint& p, SkScalar width) { 493 fWidth = width; 494 fMiterLimit = p.getStrokeMiter(); 495 fCap = (uint8_t)p.getStrokeCap(); 496 fJoin = (uint8_t)p.getStrokeJoin(); 497 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 498 } 499 500 void SkStroke::setWidth(SkScalar width) { 501 SkASSERT(width >= 0); 502 fWidth = width; 503 } 504 505 void SkStroke::setMiterLimit(SkScalar miterLimit) { 506 SkASSERT(miterLimit >= 0); 507 fMiterLimit = miterLimit; 508 } 509 510 void SkStroke::setCap(SkPaint::Cap cap) { 511 SkASSERT((unsigned)cap < SkPaint::kCapCount); 512 fCap = SkToU8(cap); 513 } 514 515 void SkStroke::setJoin(SkPaint::Join join) { 516 SkASSERT((unsigned)join < SkPaint::kJoinCount); 517 fJoin = SkToU8(join); 518 } 519 520 /////////////////////////////////////////////////////////////////////////////// 521 522 // If src==dst, then we use a tmp path to record the stroke, and then swap 523 // its contents with src when we're done. 524 class AutoTmpPath { 525 public: 526 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { 527 if (&src == *dst) { 528 *dst = &fTmpDst; 529 fSwapWithSrc = true; 530 } else { 531 (*dst)->reset(); 532 fSwapWithSrc = false; 533 } 534 } 535 536 ~AutoTmpPath() { 537 if (fSwapWithSrc) { 538 fTmpDst.swap(*const_cast<SkPath*>(&fSrc)); 539 } 540 } 541 542 private: 543 SkPath fTmpDst; 544 const SkPath& fSrc; 545 bool fSwapWithSrc; 546 }; 547 548 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { 549 SkASSERT(&src != NULL && dst != NULL); 550 551 SkScalar radius = SkScalarHalf(fWidth); 552 553 AutoTmpPath tmp(src, &dst); 554 555 if (radius <= 0) { 556 return; 557 } 558 559 // If src is really a rect, call our specialty strokeRect() method 560 { 561 bool isClosed; 562 SkPath::Direction dir; 563 if (src.isRect(&isClosed, &dir) && isClosed) { 564 this->strokeRect(src.getBounds(), dst, dir); 565 // our answer should preserve the inverseness of the src 566 if (src.isInverseFillType()) { 567 SkASSERT(!dst->isInverseFillType()); 568 dst->toggleInverseFillType(); 569 } 570 return; 571 } 572 } 573 574 SkAutoConicToQuads converter; 575 const SkScalar conicTol = SK_Scalar1 / 4; 576 577 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), 578 this->getJoin()); 579 SkPath::Iter iter(src, false); 580 SkPath::Verb lastSegment = SkPath::kMove_Verb; 581 582 for (;;) { 583 SkPoint pts[4]; 584 switch (iter.next(pts, false)) { 585 case SkPath::kMove_Verb: 586 stroker.moveTo(pts[0]); 587 break; 588 case SkPath::kLine_Verb: 589 stroker.lineTo(pts[1]); 590 lastSegment = SkPath::kLine_Verb; 591 break; 592 case SkPath::kQuad_Verb: 593 stroker.quadTo(pts[1], pts[2]); 594 lastSegment = SkPath::kQuad_Verb; 595 break; 596 case SkPath::kConic_Verb: { 597 // todo: if we had maxcurvature for conics, perhaps we should 598 // natively extrude the conic instead of converting to quads. 599 const SkPoint* quadPts = 600 converter.computeQuads(pts, iter.conicWeight(), conicTol); 601 for (int i = 0; i < converter.countQuads(); ++i) { 602 stroker.quadTo(quadPts[1], quadPts[2]); 603 quadPts += 2; 604 } 605 lastSegment = SkPath::kQuad_Verb; 606 } break; 607 case SkPath::kCubic_Verb: 608 stroker.cubicTo(pts[1], pts[2], pts[3]); 609 lastSegment = SkPath::kCubic_Verb; 610 break; 611 case SkPath::kClose_Verb: 612 stroker.close(lastSegment == SkPath::kLine_Verb); 613 break; 614 case SkPath::kDone_Verb: 615 goto DONE; 616 } 617 } 618 DONE: 619 stroker.done(dst, lastSegment == SkPath::kLine_Verb); 620 621 if (fDoFill) { 622 if (src.cheapIsDirection(SkPath::kCCW_Direction)) { 623 dst->reverseAddPath(src); 624 } else { 625 dst->addPath(src); 626 } 627 } else { 628 // Seems like we can assume that a 2-point src would always result in 629 // a convex stroke, but testing has proved otherwise. 630 // TODO: fix the stroker to make this assumption true (without making 631 // it slower that the work that will be done in computeConvexity()) 632 #if 0 633 // this test results in a non-convex stroke :( 634 static void test(SkCanvas* canvas) { 635 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; 636 SkPaint paint; 637 paint.setStrokeWidth(7); 638 paint.setStrokeCap(SkPaint::kRound_Cap); 639 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); 640 } 641 #endif 642 #if 0 643 if (2 == src.countPoints()) { 644 dst->setIsConvex(true); 645 } 646 #endif 647 } 648 649 // our answer should preserve the inverseness of the src 650 if (src.isInverseFillType()) { 651 SkASSERT(!dst->isInverseFillType()); 652 dst->toggleInverseFillType(); 653 } 654 } 655 656 static SkPath::Direction reverse_direction(SkPath::Direction dir) { 657 SkASSERT(SkPath::kUnknown_Direction != dir); 658 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 659 } 660 661 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { 662 SkPoint pts[8]; 663 664 if (SkPath::kCW_Direction == dir) { 665 pts[0].set(r.fLeft, outer.fTop); 666 pts[1].set(r.fRight, outer.fTop); 667 pts[2].set(outer.fRight, r.fTop); 668 pts[3].set(outer.fRight, r.fBottom); 669 pts[4].set(r.fRight, outer.fBottom); 670 pts[5].set(r.fLeft, outer.fBottom); 671 pts[6].set(outer.fLeft, r.fBottom); 672 pts[7].set(outer.fLeft, r.fTop); 673 } else { 674 pts[7].set(r.fLeft, outer.fTop); 675 pts[6].set(r.fRight, outer.fTop); 676 pts[5].set(outer.fRight, r.fTop); 677 pts[4].set(outer.fRight, r.fBottom); 678 pts[3].set(r.fRight, outer.fBottom); 679 pts[2].set(r.fLeft, outer.fBottom); 680 pts[1].set(outer.fLeft, r.fBottom); 681 pts[0].set(outer.fLeft, r.fTop); 682 } 683 path->addPoly(pts, 8, true); 684 } 685 686 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, 687 SkPath::Direction dir) const { 688 SkASSERT(dst != NULL); 689 dst->reset(); 690 691 SkScalar radius = SkScalarHalf(fWidth); 692 if (radius <= 0) { 693 return; 694 } 695 696 SkScalar rw = origRect.width(); 697 SkScalar rh = origRect.height(); 698 if ((rw < 0) ^ (rh < 0)) { 699 dir = reverse_direction(dir); 700 } 701 SkRect rect(origRect); 702 rect.sort(); 703 // reassign these, now that we know they'll be >= 0 704 rw = rect.width(); 705 rh = rect.height(); 706 707 SkRect r(rect); 708 r.outset(radius, radius); 709 710 SkPaint::Join join = (SkPaint::Join)fJoin; 711 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { 712 join = SkPaint::kBevel_Join; 713 } 714 715 switch (join) { 716 case SkPaint::kMiter_Join: 717 dst->addRect(r, dir); 718 break; 719 case SkPaint::kBevel_Join: 720 addBevel(dst, rect, r, dir); 721 break; 722 case SkPaint::kRound_Join: 723 dst->addRoundRect(r, radius, radius, dir); 724 break; 725 default: 726 break; 727 } 728 729 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { 730 r = rect; 731 r.inset(radius, radius); 732 dst->addRect(r, reverse_direction(dir)); 733 } 734 } 735