SkStroke.cpp revision 5408a30c8d5370db05907f2b306270df3e937543
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 4 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#ifdef SK_IGNORE_QUAD_STROKE_FIX 261 SkVector unitB; 262 SkAssertResult(set_normal_unitnormal(pts[0], pts[2], fRadius, 263 &normalB, &unitB)); 264#else 265 normalB = pts[2] - pts[0]; 266 normalB.rotateCCW(); 267 SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC); 268 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, 269 SkScalarSqrt((SK_Scalar1 + dot)/2)))); 270#endif 271 272 fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 273 pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY); 274 fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 275 pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY); 276 } 277} 278 279void SkPathStroker::cubic_to(const SkPoint pts[4], 280 const SkVector& normalAB, const SkVector& unitNormalAB, 281 SkVector* normalCD, SkVector* unitNormalCD, 282 int subDivide) { 283 SkVector ab = pts[1] - pts[0]; 284 SkVector cd = pts[3] - pts[2]; 285 SkVector normalBC, unitNormalBC; 286 287 bool degenerateAB = degenerate_vector(ab); 288 bool degenerateCD = degenerate_vector(cd); 289 290 if (degenerateAB && degenerateCD) { 291DRAW_LINE: 292 this->line_to(pts[3], normalAB); 293 *normalCD = normalAB; 294 *unitNormalCD = unitNormalAB; 295 return; 296 } 297 298 if (degenerateAB) { 299 ab = pts[2] - pts[0]; 300 degenerateAB = degenerate_vector(ab); 301 } 302 if (degenerateCD) { 303 cd = pts[3] - pts[1]; 304 degenerateCD = degenerate_vector(cd); 305 } 306 if (degenerateAB || degenerateCD) { 307 goto DRAW_LINE; 308 } 309 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); 310 bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius, 311 &normalBC, &unitNormalBC); 312 313 if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) || 314 normals_too_curvy(unitNormalBC, *unitNormalCD)) { 315 // subdivide if we can 316 if (--subDivide < 0) { 317 goto DRAW_LINE; 318 } 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 if (0) { // this is normal to the line between our adjacent pts 334 normalB = pts[2] - pts[0]; 335 normalB.rotateCCW(); 336 SkAssertResult(normalB.setLength(fRadius)); 337 338 normalC = pts[3] - pts[1]; 339 normalC.rotateCCW(); 340 SkAssertResult(normalC.setLength(fRadius)); 341 } else { // miter-join 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 357 fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 358 pts[2].fX + normalC.fX, pts[2].fY + normalC.fY, 359 pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY); 360 361 fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 362 pts[2].fX - normalC.fX, pts[2].fY - normalC.fY, 363 pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY); 364 } 365} 366 367void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { 368 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); 369 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); 370 371 if (degenerateAB | degenerateBC) { 372 if (degenerateAB ^ degenerateBC) { 373 this->lineTo(pt2); 374 } 375 return; 376 } 377 378 SkVector normalAB, unitAB, normalBC, unitBC; 379 380 this->preJoinTo(pt1, &normalAB, &unitAB, false); 381 382 { 383 SkPoint pts[3], tmp[5]; 384 pts[0] = fPrevPt; 385 pts[1] = pt1; 386 pts[2] = pt2; 387 388 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) { 389 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY); 390 unitBC.rotateCCW(); 391 if (normals_too_pinchy(unitAB, unitBC)) { 392 normalBC = unitBC; 393 normalBC.scale(fRadius); 394 395 fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY); 396 fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY); 397 fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY); 398 399 fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY); 400 fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY); 401 fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY); 402 403 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius, 404 SkPath::kCW_Direction); 405 } else { 406 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC, 407 kMaxQuadSubdivide); 408 SkVector n = normalBC; 409 SkVector u = unitBC; 410 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC, 411 kMaxQuadSubdivide); 412 } 413 } else { 414 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC, 415 kMaxQuadSubdivide); 416 } 417 } 418 419 this->postJoinTo(pt2, normalBC, unitBC); 420} 421 422void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, 423 const SkPoint& pt3) { 424 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); 425 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); 426 bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3); 427 428 if (degenerateAB + degenerateBC + degenerateCD >= 2) { 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#if 1 455 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); 456#else 457 count = 1; 458 memcpy(tmp, pts, 4 * sizeof(SkPoint)); 459#endif 460 n = normalAB; 461 u = unitAB; 462 for (i = 0; i < count; i++) { 463 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, 464 kMaxCubicSubdivide); 465 if (i == count - 1) { 466 break; 467 } 468 n = normalCD; 469 u = unitCD; 470 471 } 472 473#if 0 474 /* 475 * Why was this code here? It caused us to draw circles where we didn't 476 * want them. See http://code.google.com/p/chromium/issues/detail?id=112145 477 * and gm/dashcubics.cpp 478 * 479 * Simply removing this code seemed to fix the problem (no more circles). 480 * Wish I had a repro case earlier when I added this check/hack... 481 */ 482 // check for too pinchy 483 for (i = 1; i < count; i++) { 484 SkPoint p; 485 SkVector v, c; 486 487 SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c); 488 489 SkScalar dot = SkPoint::DotProduct(c, c); 490 v.scale(SkScalarInvert(dot)); 491 492 if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) { 493 fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction); 494 } 495 } 496#endif 497 } 498 499 this->postJoinTo(pt3, normalCD, unitCD); 500} 501 502/////////////////////////////////////////////////////////////////////////////// 503/////////////////////////////////////////////////////////////////////////////// 504 505#include "SkPaintDefaults.h" 506 507SkStroke::SkStroke() { 508 fWidth = SK_Scalar1; 509 fMiterLimit = SkPaintDefaults_MiterLimit; 510 fCap = SkPaint::kDefault_Cap; 511 fJoin = SkPaint::kDefault_Join; 512 fDoFill = false; 513} 514 515SkStroke::SkStroke(const SkPaint& p) { 516 fWidth = p.getStrokeWidth(); 517 fMiterLimit = p.getStrokeMiter(); 518 fCap = (uint8_t)p.getStrokeCap(); 519 fJoin = (uint8_t)p.getStrokeJoin(); 520 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 521} 522 523SkStroke::SkStroke(const SkPaint& p, SkScalar width) { 524 fWidth = width; 525 fMiterLimit = p.getStrokeMiter(); 526 fCap = (uint8_t)p.getStrokeCap(); 527 fJoin = (uint8_t)p.getStrokeJoin(); 528 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 529} 530 531void SkStroke::setWidth(SkScalar width) { 532 SkASSERT(width >= 0); 533 fWidth = width; 534} 535 536void SkStroke::setMiterLimit(SkScalar miterLimit) { 537 SkASSERT(miterLimit >= 0); 538 fMiterLimit = miterLimit; 539} 540 541void SkStroke::setCap(SkPaint::Cap cap) { 542 SkASSERT((unsigned)cap < SkPaint::kCapCount); 543 fCap = SkToU8(cap); 544} 545 546void SkStroke::setJoin(SkPaint::Join join) { 547 SkASSERT((unsigned)join < SkPaint::kJoinCount); 548 fJoin = SkToU8(join); 549} 550 551/////////////////////////////////////////////////////////////////////////////// 552 553#ifdef SK_SCALAR_IS_FIXED 554 /* return non-zero if the path is too big, and should be shrunk to avoid 555 overflows during intermediate calculations. Note that we compute the 556 bounds for this. If we had a custom callback/walker for paths, we could 557 perhaps go faster by using that, and just perform the abs | in that 558 routine 559 */ 560 static int needs_to_shrink(const SkPath& path) { 561 const SkRect& r = path.getBounds(); 562 SkFixed mask = SkAbs32(r.fLeft); 563 mask |= SkAbs32(r.fTop); 564 mask |= SkAbs32(r.fRight); 565 mask |= SkAbs32(r.fBottom); 566 // we need the top 3 bits clear (after abs) to avoid overflow 567 return mask >> 29; 568 } 569 570 static void identity_proc(SkPoint pts[], int count) {} 571 static void shift_down_2_proc(SkPoint pts[], int count) { 572 for (int i = 0; i < count; i++) { 573 pts->fX >>= 2; 574 pts->fY >>= 2; 575 pts += 1; 576 } 577 } 578 #define APPLY_PROC(proc, pts, count) proc(pts, count) 579#else // float does need any of this 580 #define APPLY_PROC(proc, pts, count) 581#endif 582 583// If src==dst, then we use a tmp path to record the stroke, and then swap 584// its contents with src when we're done. 585class AutoTmpPath { 586public: 587 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { 588 if (&src == *dst) { 589 *dst = &fTmpDst; 590 fSwapWithSrc = true; 591 } else { 592 (*dst)->reset(); 593 fSwapWithSrc = false; 594 } 595 } 596 597 ~AutoTmpPath() { 598 if (fSwapWithSrc) { 599 fTmpDst.swap(*const_cast<SkPath*>(&fSrc)); 600 } 601 } 602 603private: 604 SkPath fTmpDst; 605 const SkPath& fSrc; 606 bool fSwapWithSrc; 607}; 608 609void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { 610 SkASSERT(&src != NULL && dst != NULL); 611 612 SkScalar radius = SkScalarHalf(fWidth); 613 614 AutoTmpPath tmp(src, &dst); 615 616 if (radius <= 0) { 617 return; 618 } 619 620 // If src is really a rect, call our specialty strokeRect() method 621 { 622 bool isClosed; 623 SkPath::Direction dir; 624 if (src.isRect(&isClosed, &dir) && isClosed) { 625 this->strokeRect(src.getBounds(), dst, dir); 626 // our answer should preserve the inverseness of the src 627 if (src.isInverseFillType()) { 628 SkASSERT(!dst->isInverseFillType()); 629 dst->toggleInverseFillType(); 630 } 631 return; 632 } 633 } 634 635#ifdef SK_SCALAR_IS_FIXED 636 void (*proc)(SkPoint pts[], int count) = identity_proc; 637 if (needs_to_shrink(src)) { 638 proc = shift_down_2_proc; 639 radius >>= 2; 640 if (radius == 0) { 641 return; 642 } 643 } 644#endif 645 646 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), 647 this->getJoin()); 648 649 SkPath::Iter iter(src, false); 650 SkPoint pts[4]; 651 SkPath::Verb verb, lastSegment = SkPath::kMove_Verb; 652 653 while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) { 654 switch (verb) { 655 case SkPath::kMove_Verb: 656 APPLY_PROC(proc, &pts[0], 1); 657 stroker.moveTo(pts[0]); 658 break; 659 case SkPath::kLine_Verb: 660 APPLY_PROC(proc, &pts[1], 1); 661 stroker.lineTo(pts[1]); 662 lastSegment = verb; 663 break; 664 case SkPath::kQuad_Verb: 665 APPLY_PROC(proc, &pts[1], 2); 666 stroker.quadTo(pts[1], pts[2]); 667 lastSegment = verb; 668 break; 669 case SkPath::kCubic_Verb: 670 APPLY_PROC(proc, &pts[1], 3); 671 stroker.cubicTo(pts[1], pts[2], pts[3]); 672 lastSegment = verb; 673 break; 674 case SkPath::kClose_Verb: 675 stroker.close(lastSegment == SkPath::kLine_Verb); 676 break; 677 default: 678 break; 679 } 680 } 681 stroker.done(dst, lastSegment == SkPath::kLine_Verb); 682 683#ifdef SK_SCALAR_IS_FIXED 684 // undo our previous down_shift 685 if (shift_down_2_proc == proc) { 686 // need a real shift methid on path. antialias paths could use this too 687 SkMatrix matrix; 688 matrix.setScale(SkIntToScalar(4), SkIntToScalar(4)); 689 dst->transform(matrix); 690 } 691#endif 692 693 if (fDoFill) { 694 if (src.cheapIsDirection(SkPath::kCCW_Direction)) { 695 dst->reverseAddPath(src); 696 } else { 697 dst->addPath(src); 698 } 699 } else { 700 // Seems like we can assume that a 2-point src would always result in 701 // a convex stroke, but testing has proved otherwise. 702 // TODO: fix the stroker to make this assumption true (without making 703 // it slower that the work that will be done in computeConvexity()) 704#if 0 705 // this test results in a non-convex stroke :( 706 static void test(SkCanvas* canvas) { 707 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; 708 SkPaint paint; 709 paint.setStrokeWidth(7); 710 paint.setStrokeCap(SkPaint::kRound_Cap); 711 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); 712 } 713#endif 714#if 0 715 if (2 == src.countPoints()) { 716 dst->setIsConvex(true); 717 } 718#endif 719 } 720 721 // our answer should preserve the inverseness of the src 722 if (src.isInverseFillType()) { 723 SkASSERT(!dst->isInverseFillType()); 724 dst->toggleInverseFillType(); 725 } 726} 727 728static SkPath::Direction reverse_direction(SkPath::Direction dir) { 729 SkASSERT(SkPath::kUnknown_Direction != dir); 730 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 731} 732 733static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { 734 SkPoint pts[8]; 735 736 if (SkPath::kCW_Direction == dir) { 737 pts[0].set(r.fLeft, outer.fTop); 738 pts[1].set(r.fRight, outer.fTop); 739 pts[2].set(outer.fRight, r.fTop); 740 pts[3].set(outer.fRight, r.fBottom); 741 pts[4].set(r.fRight, outer.fBottom); 742 pts[5].set(r.fLeft, outer.fBottom); 743 pts[6].set(outer.fLeft, r.fBottom); 744 pts[7].set(outer.fLeft, r.fTop); 745 } else { 746 pts[7].set(r.fLeft, outer.fTop); 747 pts[6].set(r.fRight, outer.fTop); 748 pts[5].set(outer.fRight, r.fTop); 749 pts[4].set(outer.fRight, r.fBottom); 750 pts[3].set(r.fRight, outer.fBottom); 751 pts[2].set(r.fLeft, outer.fBottom); 752 pts[1].set(outer.fLeft, r.fBottom); 753 pts[0].set(outer.fLeft, r.fTop); 754 } 755 path->addPoly(pts, 8, true); 756} 757 758void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, 759 SkPath::Direction dir) const { 760 SkASSERT(dst != NULL); 761 dst->reset(); 762 763 SkScalar radius = SkScalarHalf(fWidth); 764 if (radius <= 0) { 765 return; 766 } 767 768 SkScalar rw = origRect.width(); 769 SkScalar rh = origRect.height(); 770 if ((rw < 0) ^ (rh < 0)) { 771 dir = reverse_direction(dir); 772 } 773 SkRect rect(origRect); 774 rect.sort(); 775 // reassign these, now that we know they'll be >= 0 776 rw = rect.width(); 777 rh = rect.height(); 778 779 SkRect r(rect); 780 r.outset(radius, radius); 781 782 SkPaint::Join join = (SkPaint::Join)fJoin; 783 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { 784 join = SkPaint::kBevel_Join; 785 } 786 787 switch (join) { 788 case SkPaint::kMiter_Join: 789 dst->addRect(r, dir); 790 break; 791 case SkPaint::kBevel_Join: 792 addBevel(dst, rect, r, dir); 793 break; 794 case SkPaint::kRound_Join: 795 dst->addRoundRect(r, radius, radius, dir); 796 break; 797 default: 798 break; 799 } 800 801 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { 802 r = rect; 803 r.inset(radius, radius); 804 dst->addRect(r, reverse_direction(dir)); 805 } 806} 807 808