1 /* 2 * Copyright (C) 2006-2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "SkStrokerPriv.h" 18 #include "SkGeometry.h" 19 #include "SkPath.h" 20 21 #define kMaxQuadSubdivide 5 22 #define kMaxCubicSubdivide 4 23 24 static inline bool degenerate_vector(const SkVector& v) { 25 return SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY); 26 } 27 28 static inline bool degenerate_line(const SkPoint& a, const SkPoint& b, 29 SkScalar tolerance = SK_ScalarNearlyZero) { 30 return SkScalarNearlyZero(a.fX - b.fX, tolerance) && 31 SkScalarNearlyZero(a.fY - b.fY, tolerance); 32 } 33 34 static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) { 35 /* root2/2 is a 45-degree angle 36 make this constant bigger for more subdivisions (but not >= 1) 37 */ 38 static const SkScalar kFlatEnoughNormalDotProd = 39 SK_ScalarSqrt2/2 + SK_Scalar1/10; 40 41 SkASSERT(kFlatEnoughNormalDotProd > 0 && 42 kFlatEnoughNormalDotProd < SK_Scalar1); 43 44 return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd; 45 } 46 47 static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) { 48 static const SkScalar kTooPinchyNormalDotProd = -SK_Scalar1 * 999 / 1000; 49 50 return SkPoint::DotProduct(norm0, norm1) <= kTooPinchyNormalDotProd; 51 } 52 53 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, 54 SkScalar radius, 55 SkVector* normal, SkVector* unitNormal) { 56 if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) { 57 return false; 58 } 59 unitNormal->rotateCCW(); 60 unitNormal->scale(radius, normal); 61 return true; 62 } 63 64 static bool set_normal_unitnormal(const SkVector& vec, 65 SkScalar radius, 66 SkVector* normal, SkVector* unitNormal) { 67 if (!unitNormal->setNormalize(vec.fX, vec.fY)) { 68 return false; 69 } 70 unitNormal->rotateCCW(); 71 unitNormal->scale(radius, normal); 72 return true; 73 } 74 75 /////////////////////////////////////////////////////////////////////////////// 76 77 class SkPathStroker { 78 public: 79 SkPathStroker(SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap, 80 SkPaint::Join join); 81 82 void moveTo(const SkPoint&); 83 void lineTo(const SkPoint&); 84 void quadTo(const SkPoint&, const SkPoint&); 85 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); 86 void close(bool isLine) { this->finishContour(true, isLine); } 87 88 void done(SkPath* dst, bool isLine) { 89 this->finishContour(false, isLine); 90 fOuter.addPath(fExtra); 91 dst->swap(fOuter); 92 } 93 94 private: 95 SkScalar fRadius; 96 SkScalar fInvMiterLimit; 97 98 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; 99 SkPoint fFirstPt, fPrevPt; // on original path 100 SkPoint fFirstOuterPt; 101 int fSegmentCount; 102 bool fPrevIsLine; 103 104 SkStrokerPriv::CapProc fCapper; 105 SkStrokerPriv::JoinProc fJoiner; 106 107 SkPath fInner, fOuter; // outer is our working answer, inner is temp 108 SkPath fExtra; // added as extra complete contours 109 110 void finishContour(bool close, bool isLine); 111 void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal, 112 bool isLine); 113 void postJoinTo(const SkPoint&, const SkVector& normal, 114 const SkVector& unitNormal); 115 116 void line_to(const SkPoint& currPt, const SkVector& normal); 117 void quad_to(const SkPoint pts[3], 118 const SkVector& normalAB, const SkVector& unitNormalAB, 119 SkVector* normalBC, SkVector* unitNormalBC, 120 int subDivide); 121 void cubic_to(const SkPoint pts[4], 122 const SkVector& normalAB, const SkVector& unitNormalAB, 123 SkVector* normalCD, SkVector* unitNormalCD, 124 int subDivide); 125 }; 126 127 /////////////////////////////////////////////////////////////////////////////// 128 129 void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal, 130 SkVector* unitNormal, bool currIsLine) { 131 SkASSERT(fSegmentCount >= 0); 132 133 SkScalar prevX = fPrevPt.fX; 134 SkScalar prevY = fPrevPt.fY; 135 136 SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal, 137 unitNormal)); 138 139 if (fSegmentCount == 0) { 140 fFirstNormal = *normal; 141 fFirstUnitNormal = *unitNormal; 142 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY); 143 144 fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY); 145 fInner.moveTo(prevX - normal->fX, prevY - normal->fY); 146 } else { // we have a previous segment 147 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal, 148 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine); 149 } 150 fPrevIsLine = currIsLine; 151 } 152 153 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal, 154 const SkVector& unitNormal) { 155 fPrevPt = currPt; 156 fPrevUnitNormal = unitNormal; 157 fPrevNormal = normal; 158 fSegmentCount += 1; 159 } 160 161 void SkPathStroker::finishContour(bool close, bool currIsLine) { 162 if (fSegmentCount > 0) { 163 SkPoint pt; 164 165 if (close) { 166 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, 167 fFirstUnitNormal, fRadius, fInvMiterLimit, 168 fPrevIsLine, currIsLine); 169 fOuter.close(); 170 // now add fInner as its own contour 171 fInner.getLastPt(&pt); 172 fOuter.moveTo(pt.fX, pt.fY); 173 fOuter.reversePathTo(fInner); 174 fOuter.close(); 175 } else { // add caps to start and end 176 // cap the end 177 fInner.getLastPt(&pt); 178 fCapper(&fOuter, fPrevPt, fPrevNormal, pt, 179 currIsLine ? &fInner : NULL); 180 fOuter.reversePathTo(fInner); 181 // cap the start 182 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt, 183 fPrevIsLine ? &fInner : NULL); 184 fOuter.close(); 185 } 186 } 187 fInner.reset(); 188 fSegmentCount = -1; 189 } 190 191 /////////////////////////////////////////////////////////////////////////////// 192 193 SkPathStroker::SkPathStroker(SkScalar radius, SkScalar miterLimit, 194 SkPaint::Cap cap, SkPaint::Join join) 195 : fRadius(radius) { 196 197 /* This is only used when join is miter_join, but we initialize it here 198 so that it is always defined, to fis valgrind warnings. 199 */ 200 fInvMiterLimit = 0; 201 202 if (join == SkPaint::kMiter_Join) { 203 if (miterLimit <= SK_Scalar1) { 204 join = SkPaint::kBevel_Join; 205 } else { 206 fInvMiterLimit = SkScalarInvert(miterLimit); 207 } 208 } 209 fCapper = SkStrokerPriv::CapFactory(cap); 210 fJoiner = SkStrokerPriv::JoinFactory(join); 211 fSegmentCount = -1; 212 fPrevIsLine = false; 213 } 214 215 void SkPathStroker::moveTo(const SkPoint& pt) { 216 if (fSegmentCount > 0) { 217 this->finishContour(false, false); 218 } 219 fSegmentCount = 0; 220 fFirstPt = fPrevPt = pt; 221 } 222 223 void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) { 224 fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY); 225 fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY); 226 } 227 228 void SkPathStroker::lineTo(const SkPoint& currPt) { 229 if (degenerate_line(fPrevPt, currPt)) { 230 return; 231 } 232 SkVector normal, unitNormal; 233 234 this->preJoinTo(currPt, &normal, &unitNormal, true); 235 this->line_to(currPt, normal); 236 this->postJoinTo(currPt, normal, unitNormal); 237 } 238 239 void SkPathStroker::quad_to(const SkPoint pts[3], 240 const SkVector& normalAB, const SkVector& unitNormalAB, 241 SkVector* normalBC, SkVector* unitNormalBC, 242 int subDivide) { 243 if (!set_normal_unitnormal(pts[1], pts[2], fRadius, 244 normalBC, unitNormalBC)) { 245 // pts[1] nearly equals pts[2], so just draw a line to pts[2] 246 this->line_to(pts[2], normalAB); 247 *normalBC = normalAB; 248 *unitNormalBC = unitNormalAB; 249 return; 250 } 251 252 if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) { 253 SkPoint tmp[5]; 254 SkVector norm, unit; 255 256 SkChopQuadAtHalf(pts, tmp); 257 this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide); 258 this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide); 259 } else { 260 SkVector normalB, unitB; 261 SkAssertResult(set_normal_unitnormal(pts[0], pts[2], fRadius, 262 &normalB, &unitB)); 263 264 fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 265 pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY); 266 fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 267 pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY); 268 } 269 } 270 271 void SkPathStroker::cubic_to(const SkPoint pts[4], 272 const SkVector& normalAB, const SkVector& unitNormalAB, 273 SkVector* normalCD, SkVector* unitNormalCD, 274 int subDivide) { 275 SkVector ab = pts[1] - pts[0]; 276 SkVector cd = pts[3] - pts[2]; 277 SkVector normalBC, unitNormalBC; 278 279 bool degenerateAB = degenerate_vector(ab); 280 bool degenerateCD = degenerate_vector(cd); 281 282 if (degenerateAB && degenerateCD) { 283 DRAW_LINE: 284 this->line_to(pts[3], normalAB); 285 *normalCD = normalAB; 286 *unitNormalCD = unitNormalAB; 287 return; 288 } 289 290 if (degenerateAB) { 291 ab = pts[2] - pts[0]; 292 degenerateAB = degenerate_vector(ab); 293 } 294 if (degenerateCD) { 295 cd = pts[3] - pts[1]; 296 degenerateCD = degenerate_vector(cd); 297 } 298 if (degenerateAB || degenerateCD) { 299 goto DRAW_LINE; 300 } 301 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); 302 bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius, 303 &normalBC, &unitNormalBC); 304 305 if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) || 306 normals_too_curvy(unitNormalBC, *unitNormalCD)) { 307 // subdivide if we can 308 if (--subDivide < 0) { 309 goto DRAW_LINE; 310 } 311 SkPoint tmp[7]; 312 SkVector norm, unit, dummy, unitDummy; 313 314 SkChopCubicAtHalf(pts, tmp); 315 this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, 316 subDivide); 317 // we use dummys since we already have a valid (and more accurate) 318 // normals for CD 319 this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide); 320 } else { 321 SkVector normalB, normalC; 322 323 // need normals to inset/outset the off-curve pts B and C 324 325 if (0) { // this is normal to the line between our adjacent pts 326 normalB = pts[2] - pts[0]; 327 normalB.rotateCCW(); 328 SkAssertResult(normalB.setLength(fRadius)); 329 330 normalC = pts[3] - pts[1]; 331 normalC.rotateCCW(); 332 SkAssertResult(normalC.setLength(fRadius)); 333 } else { // miter-join 334 SkVector unitBC = pts[2] - pts[1]; 335 unitBC.normalize(); 336 unitBC.rotateCCW(); 337 338 normalB = unitNormalAB + unitBC; 339 normalC = *unitNormalCD + unitBC; 340 341 SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC); 342 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, 343 SkScalarSqrt((SK_Scalar1 + dot)/2)))); 344 dot = SkPoint::DotProduct(*unitNormalCD, unitBC); 345 SkAssertResult(normalC.setLength(SkScalarDiv(fRadius, 346 SkScalarSqrt((SK_Scalar1 + dot)/2)))); 347 } 348 349 fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 350 pts[2].fX + normalC.fX, pts[2].fY + normalC.fY, 351 pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY); 352 353 fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 354 pts[2].fX - normalC.fX, pts[2].fY - normalC.fY, 355 pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY); 356 } 357 } 358 359 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { 360 bool degenerateAB = degenerate_line(fPrevPt, pt1); 361 bool degenerateBC = degenerate_line(pt1, pt2); 362 363 if (degenerateAB | degenerateBC) { 364 if (degenerateAB ^ degenerateBC) { 365 this->lineTo(pt2); 366 } 367 return; 368 } 369 370 SkVector normalAB, unitAB, normalBC, unitBC; 371 372 this->preJoinTo(pt1, &normalAB, &unitAB, false); 373 374 { 375 SkPoint pts[3], tmp[5]; 376 pts[0] = fPrevPt; 377 pts[1] = pt1; 378 pts[2] = pt2; 379 380 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) { 381 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY); 382 unitBC.rotateCCW(); 383 if (normals_too_pinchy(unitAB, unitBC)) { 384 normalBC = unitBC; 385 normalBC.scale(fRadius); 386 387 fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY); 388 fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY); 389 fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY); 390 391 fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY); 392 fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY); 393 fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY); 394 395 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius, 396 SkPath::kCW_Direction); 397 } else { 398 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC, 399 kMaxQuadSubdivide); 400 SkVector n = normalBC; 401 SkVector u = unitBC; 402 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC, 403 kMaxQuadSubdivide); 404 } 405 } else { 406 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC, 407 kMaxQuadSubdivide); 408 } 409 } 410 411 this->postJoinTo(pt2, normalBC, unitBC); 412 } 413 414 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, 415 const SkPoint& pt3) { 416 bool degenerateAB = degenerate_line(fPrevPt, pt1); 417 bool degenerateBC = degenerate_line(pt1, pt2); 418 bool degenerateCD = degenerate_line(pt2, pt3); 419 420 if (degenerateAB + degenerateBC + degenerateCD >= 2) { 421 this->lineTo(pt3); 422 return; 423 } 424 425 SkVector normalAB, unitAB, normalCD, unitCD; 426 427 // find the first tangent (which might be pt1 or pt2 428 { 429 const SkPoint* nextPt = &pt1; 430 if (degenerateAB) 431 nextPt = &pt2; 432 this->preJoinTo(*nextPt, &normalAB, &unitAB, false); 433 } 434 435 { 436 SkPoint pts[4], tmp[13]; 437 int i, count; 438 SkVector n, u; 439 SkScalar tValues[3]; 440 441 pts[0] = fPrevPt; 442 pts[1] = pt1; 443 pts[2] = pt2; 444 pts[3] = pt3; 445 446 #if 1 447 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); 448 #else 449 count = 1; 450 memcpy(tmp, pts, 4 * sizeof(SkPoint)); 451 #endif 452 n = normalAB; 453 u = unitAB; 454 for (i = 0; i < count; i++) { 455 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, 456 kMaxCubicSubdivide); 457 if (i == count - 1) { 458 break; 459 } 460 n = normalCD; 461 u = unitCD; 462 463 } 464 465 // check for too pinchy 466 for (i = 1; i < count; i++) { 467 SkPoint p; 468 SkVector v, c; 469 470 SkEvalCubicAt(pts, tValues[i - 1], &p, &v, &c); 471 472 SkScalar dot = SkPoint::DotProduct(c, c); 473 v.scale(SkScalarInvert(dot)); 474 475 if (SkScalarNearlyZero(v.fX) && SkScalarNearlyZero(v.fY)) { 476 fExtra.addCircle(p.fX, p.fY, fRadius, SkPath::kCW_Direction); 477 } 478 } 479 480 } 481 482 this->postJoinTo(pt3, normalCD, unitCD); 483 } 484 485 /////////////////////////////////////////////////////////////////////////////// 486 /////////////////////////////////////////////////////////////////////////////// 487 488 #include "SkPaint.h" 489 490 SkStroke::SkStroke() { 491 fWidth = SK_DefaultStrokeWidth; 492 fMiterLimit = SK_DefaultMiterLimit; 493 fCap = SkPaint::kDefault_Cap; 494 fJoin = SkPaint::kDefault_Join; 495 fDoFill = false; 496 } 497 498 SkStroke::SkStroke(const SkPaint& p) { 499 fWidth = p.getStrokeWidth(); 500 fMiterLimit = p.getStrokeMiter(); 501 fCap = (uint8_t)p.getStrokeCap(); 502 fJoin = (uint8_t)p.getStrokeJoin(); 503 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 504 } 505 506 SkStroke::SkStroke(const SkPaint& p, SkScalar width) { 507 fWidth = width; 508 fMiterLimit = p.getStrokeMiter(); 509 fCap = (uint8_t)p.getStrokeCap(); 510 fJoin = (uint8_t)p.getStrokeJoin(); 511 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 512 } 513 514 void SkStroke::setWidth(SkScalar width) { 515 SkASSERT(width >= 0); 516 fWidth = width; 517 } 518 519 void SkStroke::setMiterLimit(SkScalar miterLimit) { 520 SkASSERT(miterLimit >= 0); 521 fMiterLimit = miterLimit; 522 } 523 524 void SkStroke::setCap(SkPaint::Cap cap) { 525 SkASSERT((unsigned)cap < SkPaint::kCapCount); 526 fCap = SkToU8(cap); 527 } 528 529 void SkStroke::setJoin(SkPaint::Join join) { 530 SkASSERT((unsigned)join < SkPaint::kJoinCount); 531 fJoin = SkToU8(join); 532 } 533 534 /////////////////////////////////////////////////////////////////////////////// 535 536 #ifdef SK_SCALAR_IS_FIXED 537 /* return non-zero if the path is too big, and should be shrunk to avoid 538 overflows during intermediate calculations. Note that we compute the 539 bounds for this. If we had a custom callback/walker for paths, we could 540 perhaps go faster by using that, and just perform the abs | in that 541 routine 542 */ 543 static int needs_to_shrink(const SkPath& path) { 544 const SkRect& r = path.getBounds(); 545 SkFixed mask = SkAbs32(r.fLeft); 546 mask |= SkAbs32(r.fTop); 547 mask |= SkAbs32(r.fRight); 548 mask |= SkAbs32(r.fBottom); 549 // we need the top 3 bits clear (after abs) to avoid overflow 550 return mask >> 29; 551 } 552 553 static void identity_proc(SkPoint pts[], int count) {} 554 static void shift_down_2_proc(SkPoint pts[], int count) { 555 for (int i = 0; i < count; i++) { 556 pts->fX >>= 2; 557 pts->fY >>= 2; 558 pts += 1; 559 } 560 } 561 #define APPLY_PROC(proc, pts, count) proc(pts, count) 562 #else // float does need any of this 563 #define APPLY_PROC(proc, pts, count) 564 #endif 565 566 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { 567 SkASSERT(&src != NULL && dst != NULL); 568 569 SkScalar radius = SkScalarHalf(fWidth); 570 571 dst->reset(); 572 if (radius <= 0) { 573 return; 574 } 575 576 #ifdef SK_SCALAR_IS_FIXED 577 void (*proc)(SkPoint pts[], int count) = identity_proc; 578 if (needs_to_shrink(src)) { 579 proc = shift_down_2_proc; 580 radius >>= 2; 581 if (radius == 0) { 582 return; 583 } 584 } 585 #endif 586 587 SkPathStroker stroker(radius, fMiterLimit, this->getCap(), 588 this->getJoin()); 589 590 SkPath::Iter iter(src, false); 591 SkPoint pts[4]; 592 SkPath::Verb verb, lastSegment = SkPath::kMove_Verb; 593 594 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { 595 switch (verb) { 596 case SkPath::kMove_Verb: 597 APPLY_PROC(proc, &pts[0], 1); 598 stroker.moveTo(pts[0]); 599 break; 600 case SkPath::kLine_Verb: 601 APPLY_PROC(proc, &pts[1], 1); 602 stroker.lineTo(pts[1]); 603 lastSegment = verb; 604 break; 605 case SkPath::kQuad_Verb: 606 APPLY_PROC(proc, &pts[1], 2); 607 stroker.quadTo(pts[1], pts[2]); 608 lastSegment = verb; 609 break; 610 case SkPath::kCubic_Verb: 611 APPLY_PROC(proc, &pts[1], 3); 612 stroker.cubicTo(pts[1], pts[2], pts[3]); 613 lastSegment = verb; 614 break; 615 case SkPath::kClose_Verb: 616 stroker.close(lastSegment == SkPath::kLine_Verb); 617 break; 618 default: 619 break; 620 } 621 } 622 stroker.done(dst, lastSegment == SkPath::kLine_Verb); 623 624 #ifdef SK_SCALAR_IS_FIXED 625 // undo our previous down_shift 626 if (shift_down_2_proc == proc) { 627 // need a real shift methid on path. antialias paths could use this too 628 SkMatrix matrix; 629 matrix.setScale(SkIntToScalar(4), SkIntToScalar(4)); 630 dst->transform(matrix); 631 } 632 #endif 633 634 if (fDoFill) { 635 dst->addPath(src); 636 } else { 637 if (src.countPoints() == 2) { 638 dst->setIsConvex(true); 639 } 640 } 641 } 642 643 void SkStroke::strokeLine(const SkPoint& p0, const SkPoint& p1, 644 SkPath* dst) const { 645 SkPath tmp; 646 647 tmp.moveTo(p0); 648 tmp.lineTo(p1); 649 this->strokePath(tmp, dst); 650 } 651 652