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