SkStroke.cpp revision da2b21fa9ba43df374f21b0e05d9816ab1dfb876
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 15static inline bool degenerate_vector(const SkVector& v) { 16 return !SkPoint::CanNormalize(v.fX, v.fY); 17} 18 19static 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 32static 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 38static 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 49static 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 62class SkPathStroker { 63public: 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 80private: 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 115void 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 139void 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 147void 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 181SkPathStroker::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 213void SkPathStroker::moveTo(const SkPoint& pt) { 214 if (fSegmentCount > 0) { 215 this->finishContour(false, false); 216 } 217 fSegmentCount = 0; 218 fFirstPt = fPrevPt = pt; 219} 220 221void 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 226void 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 237void 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 273void 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) { 285DRAW_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 357void 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 412void 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 467SkStroke::SkStroke() { 468 fWidth = SK_Scalar1; 469 fMiterLimit = SkPaintDefaults_MiterLimit; 470 fCap = SkPaint::kDefault_Cap; 471 fJoin = SkPaint::kDefault_Join; 472 fDoFill = false; 473} 474 475SkStroke::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 483SkStroke::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 491void SkStroke::setWidth(SkScalar width) { 492 SkASSERT(width >= 0); 493 fWidth = width; 494} 495 496void SkStroke::setMiterLimit(SkScalar miterLimit) { 497 SkASSERT(miterLimit >= 0); 498 fMiterLimit = miterLimit; 499} 500 501void SkStroke::setCap(SkPaint::Cap cap) { 502 SkASSERT((unsigned)cap < SkPaint::kCapCount); 503 fCap = SkToU8(cap); 504} 505 506void 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. 515class AutoTmpPath { 516public: 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 533private: 534 SkPath fTmpDst; 535 const SkPath& fSrc; 536 bool fSwapWithSrc; 537}; 538 539void 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 SkRect rect; 553 bool isClosed; 554 SkPath::Direction dir; 555 if (src.isRect(&rect, &isClosed, &dir) && isClosed) { 556 this->strokeRect(rect, dst, dir); 557 // our answer should preserve the inverseness of the src 558 if (src.isInverseFillType()) { 559 SkASSERT(!dst->isInverseFillType()); 560 dst->toggleInverseFillType(); 561 } 562 return; 563 } 564 } 565 566 SkAutoConicToQuads converter; 567 const SkScalar conicTol = SK_Scalar1 / 4; 568 569 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), 570 this->getJoin()); 571 SkPath::Iter iter(src, false); 572 SkPath::Verb lastSegment = SkPath::kMove_Verb; 573 574 for (;;) { 575 SkPoint pts[4]; 576 switch (iter.next(pts, false)) { 577 case SkPath::kMove_Verb: 578 stroker.moveTo(pts[0]); 579 break; 580 case SkPath::kLine_Verb: 581 stroker.lineTo(pts[1]); 582 lastSegment = SkPath::kLine_Verb; 583 break; 584 case SkPath::kQuad_Verb: 585 stroker.quadTo(pts[1], pts[2]); 586 lastSegment = SkPath::kQuad_Verb; 587 break; 588 case SkPath::kConic_Verb: { 589 // todo: if we had maxcurvature for conics, perhaps we should 590 // natively extrude the conic instead of converting to quads. 591 const SkPoint* quadPts = 592 converter.computeQuads(pts, iter.conicWeight(), conicTol); 593 for (int i = 0; i < converter.countQuads(); ++i) { 594 stroker.quadTo(quadPts[1], quadPts[2]); 595 quadPts += 2; 596 } 597 lastSegment = SkPath::kQuad_Verb; 598 } break; 599 case SkPath::kCubic_Verb: 600 stroker.cubicTo(pts[1], pts[2], pts[3]); 601 lastSegment = SkPath::kCubic_Verb; 602 break; 603 case SkPath::kClose_Verb: 604 stroker.close(lastSegment == SkPath::kLine_Verb); 605 break; 606 case SkPath::kDone_Verb: 607 goto DONE; 608 } 609 } 610DONE: 611 stroker.done(dst, lastSegment == SkPath::kLine_Verb); 612 613 if (fDoFill) { 614 if (src.cheapIsDirection(SkPath::kCCW_Direction)) { 615 dst->reverseAddPath(src); 616 } else { 617 dst->addPath(src); 618 } 619 } else { 620 // Seems like we can assume that a 2-point src would always result in 621 // a convex stroke, but testing has proved otherwise. 622 // TODO: fix the stroker to make this assumption true (without making 623 // it slower that the work that will be done in computeConvexity()) 624#if 0 625 // this test results in a non-convex stroke :( 626 static void test(SkCanvas* canvas) { 627 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; 628 SkPaint paint; 629 paint.setStrokeWidth(7); 630 paint.setStrokeCap(SkPaint::kRound_Cap); 631 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); 632 } 633#endif 634#if 0 635 if (2 == src.countPoints()) { 636 dst->setIsConvex(true); 637 } 638#endif 639 } 640 641 // our answer should preserve the inverseness of the src 642 if (src.isInverseFillType()) { 643 SkASSERT(!dst->isInverseFillType()); 644 dst->toggleInverseFillType(); 645 } 646} 647 648static SkPath::Direction reverse_direction(SkPath::Direction dir) { 649 SkASSERT(SkPath::kUnknown_Direction != dir); 650 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 651} 652 653static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { 654 SkPoint pts[8]; 655 656 if (SkPath::kCW_Direction == dir) { 657 pts[0].set(r.fLeft, outer.fTop); 658 pts[1].set(r.fRight, outer.fTop); 659 pts[2].set(outer.fRight, r.fTop); 660 pts[3].set(outer.fRight, r.fBottom); 661 pts[4].set(r.fRight, outer.fBottom); 662 pts[5].set(r.fLeft, outer.fBottom); 663 pts[6].set(outer.fLeft, r.fBottom); 664 pts[7].set(outer.fLeft, r.fTop); 665 } else { 666 pts[7].set(r.fLeft, outer.fTop); 667 pts[6].set(r.fRight, outer.fTop); 668 pts[5].set(outer.fRight, r.fTop); 669 pts[4].set(outer.fRight, r.fBottom); 670 pts[3].set(r.fRight, outer.fBottom); 671 pts[2].set(r.fLeft, outer.fBottom); 672 pts[1].set(outer.fLeft, r.fBottom); 673 pts[0].set(outer.fLeft, r.fTop); 674 } 675 path->addPoly(pts, 8, true); 676} 677 678void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, 679 SkPath::Direction dir) const { 680 SkASSERT(dst != NULL); 681 dst->reset(); 682 683 SkScalar radius = SkScalarHalf(fWidth); 684 if (radius <= 0) { 685 return; 686 } 687 688 SkScalar rw = origRect.width(); 689 SkScalar rh = origRect.height(); 690 if ((rw < 0) ^ (rh < 0)) { 691 dir = reverse_direction(dir); 692 } 693 SkRect rect(origRect); 694 rect.sort(); 695 // reassign these, now that we know they'll be >= 0 696 rw = rect.width(); 697 rh = rect.height(); 698 699 SkRect r(rect); 700 r.outset(radius, radius); 701 702 SkPaint::Join join = (SkPaint::Join)fJoin; 703 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { 704 join = SkPaint::kBevel_Join; 705 } 706 707 switch (join) { 708 case SkPaint::kMiter_Join: 709 dst->addRect(r, dir); 710 break; 711 case SkPaint::kBevel_Join: 712 addBevel(dst, rect, r, dir); 713 break; 714 case SkPaint::kRound_Join: 715 dst->addRoundRect(r, radius, radius, dir); 716 break; 717 default: 718 break; 719 } 720 721 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { 722 r = rect; 723 r.inset(radius, radius); 724 dst->addRect(r, reverse_direction(dir)); 725 } 726} 727