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 || (degenerateAB && SkPath::IsLineDegenerate(fPrevPt, pt2))) { 429 this->lineTo(pt3); 430 return; 431 } 432 433 SkVector normalAB, unitAB, normalCD, unitCD; 434 435 // find the first tangent (which might be pt1 or pt2 436 { 437 const SkPoint* nextPt = &pt1; 438 if (degenerateAB) 439 nextPt = &pt2; 440 this->preJoinTo(*nextPt, &normalAB, &unitAB, false); 441 } 442 443 { 444 SkPoint pts[4], tmp[13]; 445 int i, count; 446 SkVector n, u; 447 SkScalar tValues[3]; 448 449 pts[0] = fPrevPt; 450 pts[1] = pt1; 451 pts[2] = pt2; 452 pts[3] = pt3; 453 454 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); 455 n = normalAB; 456 u = unitAB; 457 for (i = 0; i < count; i++) { 458 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, 459 kMaxCubicSubdivide); 460 if (i == count - 1) { 461 break; 462 } 463 n = normalCD; 464 u = unitCD; 465 466 } 467 } 468 469 this->postJoinTo(pt3, normalCD, unitCD); 470 } 471 472 /////////////////////////////////////////////////////////////////////////////// 473 /////////////////////////////////////////////////////////////////////////////// 474 475 #include "SkPaintDefaults.h" 476 477 SkStroke::SkStroke() { 478 fWidth = SK_Scalar1; 479 fMiterLimit = SkPaintDefaults_MiterLimit; 480 fCap = SkPaint::kDefault_Cap; 481 fJoin = SkPaint::kDefault_Join; 482 fDoFill = false; 483 } 484 485 SkStroke::SkStroke(const SkPaint& p) { 486 fWidth = p.getStrokeWidth(); 487 fMiterLimit = p.getStrokeMiter(); 488 fCap = (uint8_t)p.getStrokeCap(); 489 fJoin = (uint8_t)p.getStrokeJoin(); 490 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 491 } 492 493 SkStroke::SkStroke(const SkPaint& p, SkScalar width) { 494 fWidth = width; 495 fMiterLimit = p.getStrokeMiter(); 496 fCap = (uint8_t)p.getStrokeCap(); 497 fJoin = (uint8_t)p.getStrokeJoin(); 498 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 499 } 500 501 void SkStroke::setWidth(SkScalar width) { 502 SkASSERT(width >= 0); 503 fWidth = width; 504 } 505 506 void SkStroke::setMiterLimit(SkScalar miterLimit) { 507 SkASSERT(miterLimit >= 0); 508 fMiterLimit = miterLimit; 509 } 510 511 void SkStroke::setCap(SkPaint::Cap cap) { 512 SkASSERT((unsigned)cap < SkPaint::kCapCount); 513 fCap = SkToU8(cap); 514 } 515 516 void SkStroke::setJoin(SkPaint::Join join) { 517 SkASSERT((unsigned)join < SkPaint::kJoinCount); 518 fJoin = SkToU8(join); 519 } 520 521 /////////////////////////////////////////////////////////////////////////////// 522 523 // If src==dst, then we use a tmp path to record the stroke, and then swap 524 // its contents with src when we're done. 525 class AutoTmpPath { 526 public: 527 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { 528 if (&src == *dst) { 529 *dst = &fTmpDst; 530 fSwapWithSrc = true; 531 } else { 532 (*dst)->reset(); 533 fSwapWithSrc = false; 534 } 535 } 536 537 ~AutoTmpPath() { 538 if (fSwapWithSrc) { 539 fTmpDst.swap(*const_cast<SkPath*>(&fSrc)); 540 } 541 } 542 543 private: 544 SkPath fTmpDst; 545 const SkPath& fSrc; 546 bool fSwapWithSrc; 547 }; 548 549 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { 550 SkASSERT(&src != NULL && dst != NULL); 551 552 SkScalar radius = SkScalarHalf(fWidth); 553 554 AutoTmpPath tmp(src, &dst); 555 556 if (radius <= 0) { 557 return; 558 } 559 560 // If src is really a rect, call our specialty strokeRect() method 561 { 562 bool isClosed; 563 SkPath::Direction dir; 564 if (src.isRect(&isClosed, &dir) && isClosed) { 565 this->strokeRect(src.getBounds(), dst, dir); 566 // our answer should preserve the inverseness of the src 567 if (src.isInverseFillType()) { 568 SkASSERT(!dst->isInverseFillType()); 569 dst->toggleInverseFillType(); 570 } 571 return; 572 } 573 } 574 575 SkAutoConicToQuads converter; 576 const SkScalar conicTol = SK_Scalar1 / 4; 577 578 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), 579 this->getJoin()); 580 SkPath::Iter iter(src, false); 581 SkPath::Verb lastSegment = SkPath::kMove_Verb; 582 583 for (;;) { 584 SkPoint pts[4]; 585 switch (iter.next(pts, false)) { 586 case SkPath::kMove_Verb: 587 stroker.moveTo(pts[0]); 588 break; 589 case SkPath::kLine_Verb: 590 stroker.lineTo(pts[1]); 591 lastSegment = SkPath::kLine_Verb; 592 break; 593 case SkPath::kQuad_Verb: 594 stroker.quadTo(pts[1], pts[2]); 595 lastSegment = SkPath::kQuad_Verb; 596 break; 597 case SkPath::kConic_Verb: { 598 // todo: if we had maxcurvature for conics, perhaps we should 599 // natively extrude the conic instead of converting to quads. 600 const SkPoint* quadPts = 601 converter.computeQuads(pts, iter.conicWeight(), conicTol); 602 for (int i = 0; i < converter.countQuads(); ++i) { 603 stroker.quadTo(quadPts[1], quadPts[2]); 604 quadPts += 2; 605 } 606 lastSegment = SkPath::kQuad_Verb; 607 } break; 608 case SkPath::kCubic_Verb: 609 stroker.cubicTo(pts[1], pts[2], pts[3]); 610 lastSegment = SkPath::kCubic_Verb; 611 break; 612 case SkPath::kClose_Verb: 613 stroker.close(lastSegment == SkPath::kLine_Verb); 614 break; 615 case SkPath::kDone_Verb: 616 goto DONE; 617 } 618 } 619 DONE: 620 stroker.done(dst, lastSegment == SkPath::kLine_Verb); 621 622 if (fDoFill) { 623 if (src.cheapIsDirection(SkPath::kCCW_Direction)) { 624 dst->reverseAddPath(src); 625 } else { 626 dst->addPath(src); 627 } 628 } else { 629 // Seems like we can assume that a 2-point src would always result in 630 // a convex stroke, but testing has proved otherwise. 631 // TODO: fix the stroker to make this assumption true (without making 632 // it slower that the work that will be done in computeConvexity()) 633 #if 0 634 // this test results in a non-convex stroke :( 635 static void test(SkCanvas* canvas) { 636 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; 637 SkPaint paint; 638 paint.setStrokeWidth(7); 639 paint.setStrokeCap(SkPaint::kRound_Cap); 640 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); 641 } 642 #endif 643 #if 0 644 if (2 == src.countPoints()) { 645 dst->setIsConvex(true); 646 } 647 #endif 648 } 649 650 // our answer should preserve the inverseness of the src 651 if (src.isInverseFillType()) { 652 SkASSERT(!dst->isInverseFillType()); 653 dst->toggleInverseFillType(); 654 } 655 } 656 657 static SkPath::Direction reverse_direction(SkPath::Direction dir) { 658 SkASSERT(SkPath::kUnknown_Direction != dir); 659 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 660 } 661 662 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { 663 SkPoint pts[8]; 664 665 if (SkPath::kCW_Direction == dir) { 666 pts[0].set(r.fLeft, outer.fTop); 667 pts[1].set(r.fRight, outer.fTop); 668 pts[2].set(outer.fRight, r.fTop); 669 pts[3].set(outer.fRight, r.fBottom); 670 pts[4].set(r.fRight, outer.fBottom); 671 pts[5].set(r.fLeft, outer.fBottom); 672 pts[6].set(outer.fLeft, r.fBottom); 673 pts[7].set(outer.fLeft, r.fTop); 674 } else { 675 pts[7].set(r.fLeft, outer.fTop); 676 pts[6].set(r.fRight, outer.fTop); 677 pts[5].set(outer.fRight, r.fTop); 678 pts[4].set(outer.fRight, r.fBottom); 679 pts[3].set(r.fRight, outer.fBottom); 680 pts[2].set(r.fLeft, outer.fBottom); 681 pts[1].set(outer.fLeft, r.fBottom); 682 pts[0].set(outer.fLeft, r.fTop); 683 } 684 path->addPoly(pts, 8, true); 685 } 686 687 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, 688 SkPath::Direction dir) const { 689 SkASSERT(dst != NULL); 690 dst->reset(); 691 692 SkScalar radius = SkScalarHalf(fWidth); 693 if (radius <= 0) { 694 return; 695 } 696 697 SkScalar rw = origRect.width(); 698 SkScalar rh = origRect.height(); 699 if ((rw < 0) ^ (rh < 0)) { 700 dir = reverse_direction(dir); 701 } 702 SkRect rect(origRect); 703 rect.sort(); 704 // reassign these, now that we know they'll be >= 0 705 rw = rect.width(); 706 rh = rect.height(); 707 708 SkRect r(rect); 709 r.outset(radius, radius); 710 711 SkPaint::Join join = (SkPaint::Join)fJoin; 712 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { 713 join = SkPaint::kBevel_Join; 714 } 715 716 switch (join) { 717 case SkPaint::kMiter_Join: 718 dst->addRect(r, dir); 719 break; 720 case SkPaint::kBevel_Join: 721 addBevel(dst, rect, r, dir); 722 break; 723 case SkPaint::kRound_Join: 724 dst->addRoundRect(r, radius, radius, dir); 725 break; 726 default: 727 break; 728 } 729 730 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { 731 r = rect; 732 r.inset(radius, radius); 733 dst->addRect(r, reverse_direction(dir)); 734 } 735 } 736