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