SkPath.cpp revision 40528743dbb9ce7f39f093e0cdc47849ac8887cf
1/* libs/graphics/sgl/SkPath.cpp 2** 3** Copyright 2006, The Android Open Source Project 4** 5** Licensed under the Apache License, Version 2.0 (the "License"); 6** you may not use this file except in compliance with the License. 7** You may obtain a copy of the License at 8** 9** http://www.apache.org/licenses/LICENSE-2.0 10** 11** Unless required by applicable law or agreed to in writing, software 12** distributed under the License is distributed on an "AS IS" BASIS, 13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14** See the License for the specific language governing permissions and 15** limitations under the License. 16*/ 17 18#include "SkPath.h" 19#include "SkFlattenable.h" 20#include "SkMath.h" 21 22//////////////////////////////////////////////////////////////////////////// 23 24/* This guy's constructor/destructor bracket a path editing operation. It is 25 used when we know the bounds of the amount we are going to add to the path 26 (usually a new contour, but not required). 27 28 It captures some state about the path up front (i.e. if it already has a 29 cached bounds), and the if it can, it updates the cache bounds explicitly, 30 avoiding the need to revisit all of the points in getBounds(). 31 32 It also notes if the path was originally empty, and if so, sets isConvex 33 to true. Thus it can only be used if the contour being added is convex. 34 */ 35class SkAutoPathBoundsUpdate { 36public: 37 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) { 38 this->init(path); 39 } 40 41 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top, 42 SkScalar right, SkScalar bottom) { 43 fRect.set(left, top, right, bottom); 44 this->init(path); 45 } 46 47 ~SkAutoPathBoundsUpdate() { 48 fPath->setIsConvex(fEmpty); 49 if (fEmpty) { 50 fPath->fBounds = fRect; 51 fPath->fBoundsIsDirty = false; 52 } else if (!fDirty) { 53 fPath->fBounds.join(fRect); 54 fPath->fBoundsIsDirty = false; 55 } 56 } 57 58private: 59 SkPath* fPath; 60 SkRect fRect; 61 bool fDirty; 62 bool fEmpty; 63 64 // returns true if we should proceed 65 void init(SkPath* path) { 66 fPath = path; 67 fDirty = SkToBool(path->fBoundsIsDirty); 68 fEmpty = path->isEmpty(); 69 // Cannot use fRect for our bounds unless we know it is sorted 70 fRect.sort(); 71 } 72}; 73 74static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) { 75 if (pts.count() <= 1) { // we ignore just 1 point (moveto) 76 bounds->set(0, 0, 0, 0); 77 } else { 78 bounds->set(pts.begin(), pts.count()); 79// SkDebugf("------- compute bounds %p %d", &pts, pts.count()); 80 } 81} 82 83//////////////////////////////////////////////////////////////////////////// 84 85/* 86 Stores the verbs and points as they are given to us, with exceptions: 87 - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic 88 - we insert a Move(0,0) if Line | Quad | Cubic is our first command 89 90 The iterator does more cleanup, especially if forceClose == true 91 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt) 92 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1 93 3. if we encounter Line | Quad | Cubic after Close, cons up a Move 94*/ 95 96//////////////////////////////////////////////////////////////////////////// 97 98SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) { 99 fIsConvex = false; 100 fGenerationID = 0; 101} 102 103SkPath::SkPath(const SkPath& src) { 104 SkDEBUGCODE(src.validate();) 105 *this = src; 106} 107 108SkPath::~SkPath() { 109 SkDEBUGCODE(this->validate();) 110} 111 112SkPath& SkPath::operator=(const SkPath& src) { 113 SkDEBUGCODE(src.validate();) 114 115 if (this != &src) { 116 fBounds = src.fBounds; 117 fPts = src.fPts; 118 fVerbs = src.fVerbs; 119 fFillType = src.fFillType; 120 fBoundsIsDirty = src.fBoundsIsDirty; 121 fIsConvex = src.fIsConvex; 122 fGenerationID++; 123 } 124 SkDEBUGCODE(this->validate();) 125 return *this; 126} 127 128bool operator==(const SkPath& a, const SkPath& b) { 129 // note: don't need to look at isConvex or bounds, since just comparing the 130 // raw data is sufficient. 131 return &a == &b || 132 (a.fFillType == b.fFillType && a.fVerbs == b.fVerbs && a.fPts == b.fPts); 133} 134 135void SkPath::swap(SkPath& other) { 136 SkASSERT(&other != NULL); 137 138 if (this != &other) { 139 SkTSwap<SkRect>(fBounds, other.fBounds); 140 fPts.swap(other.fPts); 141 fVerbs.swap(other.fVerbs); 142 SkTSwap<uint8_t>(fFillType, other.fFillType); 143 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty); 144 SkTSwap<uint8_t>(fIsConvex, other.fIsConvex); 145 fGenerationID++; 146 } 147} 148 149uint32_t SkPath::getGenerationID() const { 150 return fGenerationID; 151} 152 153void SkPath::reset() { 154 SkDEBUGCODE(this->validate();) 155 156 fPts.reset(); 157 fVerbs.reset(); 158 fGenerationID++; 159 fBoundsIsDirty = true; 160} 161 162void SkPath::rewind() { 163 SkDEBUGCODE(this->validate();) 164 165 fPts.rewind(); 166 fVerbs.rewind(); 167 fGenerationID++; 168 fBoundsIsDirty = true; 169} 170 171bool SkPath::isEmpty() const { 172 SkDEBUGCODE(this->validate();) 173 174 int count = fVerbs.count(); 175 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb); 176} 177 178bool SkPath::isRect(SkRect*) const { 179 SkDEBUGCODE(this->validate();) 180 181 SkASSERT(!"unimplemented"); 182 return false; 183} 184 185int SkPath::getPoints(SkPoint copy[], int max) const { 186 SkDEBUGCODE(this->validate();) 187 188 SkASSERT(max >= 0); 189 int count = fPts.count(); 190 if (copy && max > 0 && count > 0) { 191 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count)); 192 } 193 return count; 194} 195 196SkPoint SkPath::getPoint(int index) const { 197 if ((unsigned)index < (unsigned)fPts.count()) { 198 return fPts[index]; 199 } 200 return SkPoint::Make(0, 0); 201} 202 203void SkPath::getLastPt(SkPoint* lastPt) const { 204 SkDEBUGCODE(this->validate();) 205 206 if (lastPt) { 207 int count = fPts.count(); 208 if (count == 0) { 209 lastPt->set(0, 0); 210 } else { 211 *lastPt = fPts[count - 1]; 212 } 213 } 214} 215 216void SkPath::setLastPt(SkScalar x, SkScalar y) { 217 SkDEBUGCODE(this->validate();) 218 219 int count = fPts.count(); 220 if (count == 0) { 221 this->moveTo(x, y); 222 } else { 223 fPts[count - 1].set(x, y); 224 fGenerationID++; 225 } 226} 227 228void SkPath::computeBounds() const { 229 SkDEBUGCODE(this->validate();) 230 SkASSERT(fBoundsIsDirty); 231 232 fBoundsIsDirty = false; 233 compute_pt_bounds(&fBounds, fPts); 234} 235 236////////////////////////////////////////////////////////////////////////////// 237// Construction methods 238 239void SkPath::incReserve(U16CPU inc) { 240 SkDEBUGCODE(this->validate();) 241 242 fVerbs.setReserve(fVerbs.count() + inc); 243 fPts.setReserve(fPts.count() + inc); 244 245 SkDEBUGCODE(this->validate();) 246} 247 248void SkPath::moveTo(SkScalar x, SkScalar y) { 249 SkDEBUGCODE(this->validate();) 250 251 int vc = fVerbs.count(); 252 SkPoint* pt; 253 254 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) { 255 pt = &fPts[fPts.count() - 1]; 256 } else { 257 pt = fPts.append(); 258 *fVerbs.append() = kMove_Verb; 259 } 260 pt->set(x, y); 261 262 fGenerationID++; 263 fBoundsIsDirty = true; 264} 265 266void SkPath::rMoveTo(SkScalar x, SkScalar y) { 267 SkPoint pt; 268 this->getLastPt(&pt); 269 this->moveTo(pt.fX + x, pt.fY + y); 270} 271 272void SkPath::lineTo(SkScalar x, SkScalar y) { 273 SkDEBUGCODE(this->validate();) 274 275 if (fVerbs.count() == 0) { 276 fPts.append()->set(0, 0); 277 *fVerbs.append() = kMove_Verb; 278 } 279 fPts.append()->set(x, y); 280 *fVerbs.append() = kLine_Verb; 281 282 fGenerationID++; 283 fBoundsIsDirty = true; 284} 285 286void SkPath::rLineTo(SkScalar x, SkScalar y) { 287 SkPoint pt; 288 this->getLastPt(&pt); 289 this->lineTo(pt.fX + x, pt.fY + y); 290} 291 292void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { 293 SkDEBUGCODE(this->validate();) 294 295 if (fVerbs.count() == 0) { 296 fPts.append()->set(0, 0); 297 *fVerbs.append() = kMove_Verb; 298 } 299 300 SkPoint* pts = fPts.append(2); 301 pts[0].set(x1, y1); 302 pts[1].set(x2, y2); 303 *fVerbs.append() = kQuad_Verb; 304 305 fGenerationID++; 306 fBoundsIsDirty = true; 307} 308 309void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { 310 SkPoint pt; 311 this->getLastPt(&pt); 312 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2); 313} 314 315void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, 316 SkScalar x3, SkScalar y3) { 317 SkDEBUGCODE(this->validate();) 318 319 if (fVerbs.count() == 0) { 320 fPts.append()->set(0, 0); 321 *fVerbs.append() = kMove_Verb; 322 } 323 SkPoint* pts = fPts.append(3); 324 pts[0].set(x1, y1); 325 pts[1].set(x2, y2); 326 pts[2].set(x3, y3); 327 *fVerbs.append() = kCubic_Verb; 328 329 fGenerationID++; 330 fBoundsIsDirty = true; 331} 332 333void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, 334 SkScalar x3, SkScalar y3) { 335 SkPoint pt; 336 this->getLastPt(&pt); 337 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2, 338 pt.fX + x3, pt.fY + y3); 339} 340 341void SkPath::close() { 342 SkDEBUGCODE(this->validate();) 343 344 int count = fVerbs.count(); 345 if (count > 0) { 346 switch (fVerbs[count - 1]) { 347 case kLine_Verb: 348 case kQuad_Verb: 349 case kCubic_Verb: 350 *fVerbs.append() = kClose_Verb; 351 fGenerationID++; 352 break; 353 default: 354 // don't add a close if the prev wasn't a primitive 355 break; 356 } 357 } 358} 359 360/////////////////////////////////////////////////////////////////////////////// 361 362void SkPath::addRect(const SkRect& rect, Direction dir) { 363 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir); 364} 365 366void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right, 367 SkScalar bottom, Direction dir) { 368 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom); 369 370 this->incReserve(5); 371 372 this->moveTo(left, top); 373 if (dir == kCCW_Direction) { 374 this->lineTo(left, bottom); 375 this->lineTo(right, bottom); 376 this->lineTo(right, top); 377 } else { 378 this->lineTo(right, top); 379 this->lineTo(right, bottom); 380 this->lineTo(left, bottom); 381 } 382 this->close(); 383} 384 385#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3) 386 387void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, 388 Direction dir) { 389 SkScalar w = rect.width(); 390 SkScalar halfW = SkScalarHalf(w); 391 SkScalar h = rect.height(); 392 SkScalar halfH = SkScalarHalf(h); 393 394 if (halfW <= 0 || halfH <= 0) { 395 return; 396 } 397 398 bool skip_hori = rx >= halfW; 399 bool skip_vert = ry >= halfH; 400 401 if (skip_hori && skip_vert) { 402 this->addOval(rect, dir); 403 return; 404 } 405 406 SkAutoPathBoundsUpdate apbu(this, rect); 407 408 if (skip_hori) { 409 rx = halfW; 410 } else if (skip_vert) { 411 ry = halfH; 412 } 413 414 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR); 415 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR); 416 417 this->incReserve(17); 418 this->moveTo(rect.fRight - rx, rect.fTop); 419 if (dir == kCCW_Direction) { 420 if (!skip_hori) { 421 this->lineTo(rect.fLeft + rx, rect.fTop); // top 422 } 423 this->cubicTo(rect.fLeft + rx - sx, rect.fTop, 424 rect.fLeft, rect.fTop + ry - sy, 425 rect.fLeft, rect.fTop + ry); // top-left 426 if (!skip_vert) { 427 this->lineTo(rect.fLeft, rect.fBottom - ry); // left 428 } 429 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy, 430 rect.fLeft + rx - sx, rect.fBottom, 431 rect.fLeft + rx, rect.fBottom); // bot-left 432 if (!skip_hori) { 433 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom 434 } 435 this->cubicTo(rect.fRight - rx + sx, rect.fBottom, 436 rect.fRight, rect.fBottom - ry + sy, 437 rect.fRight, rect.fBottom - ry); // bot-right 438 if (!skip_vert) { 439 this->lineTo(rect.fRight, rect.fTop + ry); 440 } 441 this->cubicTo(rect.fRight, rect.fTop + ry - sy, 442 rect.fRight - rx + sx, rect.fTop, 443 rect.fRight - rx, rect.fTop); // top-right 444 } else { 445 this->cubicTo(rect.fRight - rx + sx, rect.fTop, 446 rect.fRight, rect.fTop + ry - sy, 447 rect.fRight, rect.fTop + ry); // top-right 448 if (!skip_vert) { 449 this->lineTo(rect.fRight, rect.fBottom - ry); 450 } 451 this->cubicTo(rect.fRight, rect.fBottom - ry + sy, 452 rect.fRight - rx + sx, rect.fBottom, 453 rect.fRight - rx, rect.fBottom); // bot-right 454 if (!skip_hori) { 455 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom 456 } 457 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom, 458 rect.fLeft, rect.fBottom - ry + sy, 459 rect.fLeft, rect.fBottom - ry); // bot-left 460 if (!skip_vert) { 461 this->lineTo(rect.fLeft, rect.fTop + ry); // left 462 } 463 this->cubicTo(rect.fLeft, rect.fTop + ry - sy, 464 rect.fLeft + rx - sx, rect.fTop, 465 rect.fLeft + rx, rect.fTop); // top-left 466 if (!skip_hori) { 467 this->lineTo(rect.fRight - rx, rect.fTop); // top 468 } 469 } 470 this->close(); 471} 472 473static void add_corner_arc(SkPath* path, const SkRect& rect, 474 SkScalar rx, SkScalar ry, int startAngle, 475 SkPath::Direction dir, bool forceMoveTo) { 476 rx = SkMinScalar(SkScalarHalf(rect.width()), rx); 477 ry = SkMinScalar(SkScalarHalf(rect.height()), ry); 478 479 SkRect r; 480 r.set(-rx, -ry, rx, ry); 481 482 switch (startAngle) { 483 case 0: 484 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom); 485 break; 486 case 90: 487 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom); 488 break; 489 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break; 490 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break; 491 default: SkASSERT(!"unexpected startAngle in add_corner_arc"); 492 } 493 494 SkScalar start = SkIntToScalar(startAngle); 495 SkScalar sweep = SkIntToScalar(90); 496 if (SkPath::kCCW_Direction == dir) { 497 start += sweep; 498 sweep = -sweep; 499 } 500 501 path->arcTo(r, start, sweep, forceMoveTo); 502} 503 504void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[], 505 Direction dir) { 506 // abort before we invoke SkAutoPathBoundsUpdate() 507 if (rect.isEmpty()) { 508 return; 509 } 510 511 SkAutoPathBoundsUpdate apbu(this, rect); 512 513 if (kCW_Direction == dir) { 514 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true); 515 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false); 516 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false); 517 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false); 518 } else { 519 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true); 520 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false); 521 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false); 522 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false); 523 } 524 this->close(); 525} 526 527void SkPath::addOval(const SkRect& oval, Direction dir) { 528 SkAutoPathBoundsUpdate apbu(this, oval); 529 530 SkScalar cx = oval.centerX(); 531 SkScalar cy = oval.centerY(); 532 SkScalar rx = SkScalarHalf(oval.width()); 533 SkScalar ry = SkScalarHalf(oval.height()); 534#if 0 // these seem faster than using quads (1/2 the number of edges) 535 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR); 536 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR); 537 538 this->incReserve(13); 539 this->moveTo(cx + rx, cy); 540 if (dir == kCCW_Direction) { 541 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry); 542 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy); 543 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry); 544 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy); 545 } else { 546 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry); 547 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy); 548 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry); 549 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy); 550 } 551#else 552 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8); 553 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8); 554 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2); 555 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2); 556 557 /* 558 To handle imprecision in computing the center and radii, we revert to 559 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx) 560 to ensure that we don't exceed the oval's bounds *ever*, since we want 561 to use oval for our fast-bounds, rather than have to recompute it. 562 */ 563 const SkScalar L = oval.fLeft; // cx - rx 564 const SkScalar T = oval.fTop; // cy - ry 565 const SkScalar R = oval.fRight; // cx + rx 566 const SkScalar B = oval.fBottom; // cy + ry 567 568 this->incReserve(17); // 8 quads + close 569 this->moveTo(R, cy); 570 if (dir == kCCW_Direction) { 571 this->quadTo( R, cy - sy, cx + mx, cy - my); 572 this->quadTo(cx + sx, T, cx , T); 573 this->quadTo(cx - sx, T, cx - mx, cy - my); 574 this->quadTo( L, cy - sy, L, cy ); 575 this->quadTo( L, cy + sy, cx - mx, cy + my); 576 this->quadTo(cx - sx, B, cx , B); 577 this->quadTo(cx + sx, B, cx + mx, cy + my); 578 this->quadTo( R, cy + sy, R, cy ); 579 } else { 580 this->quadTo( R, cy + sy, cx + mx, cy + my); 581 this->quadTo(cx + sx, B, cx , B); 582 this->quadTo(cx - sx, B, cx - mx, cy + my); 583 this->quadTo( L, cy + sy, L, cy ); 584 this->quadTo( L, cy - sy, cx - mx, cy - my); 585 this->quadTo(cx - sx, T, cx , T); 586 this->quadTo(cx + sx, T, cx + mx, cy - my); 587 this->quadTo( R, cy - sy, R, cy ); 588 } 589#endif 590 this->close(); 591} 592 593void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) { 594 if (r > 0) { 595 SkRect rect; 596 rect.set(x - r, y - r, x + r, y + r); 597 this->addOval(rect, dir); 598 } 599} 600 601#include "SkGeometry.h" 602 603static int build_arc_points(const SkRect& oval, SkScalar startAngle, 604 SkScalar sweepAngle, 605 SkPoint pts[kSkBuildQuadArcStorage]) { 606 SkVector start, stop; 607 608 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX); 609 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), 610 &stop.fX); 611 612 /* If the sweep angle is nearly (but less than) 360, then due to precision 613 loss in radians-conversion and/or sin/cos, we may end up with coincident 614 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead 615 of drawing a nearly complete circle (good). 616 e.g. canvas.drawArc(0, 359.99, ...) 617 -vs- canvas.drawArc(0, 359.9, ...) 618 We try to detect this edge case, and tweak the stop vector 619 */ 620 if (start == stop) { 621 SkScalar sw = SkScalarAbs(sweepAngle); 622 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) { 623 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle); 624 // make a guess at a tiny angle (in radians) to tweak by 625 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle); 626 // not sure how much will be enough, so we use a loop 627 do { 628 stopRad -= deltaRad; 629 stop.fY = SkScalarSinCos(stopRad, &stop.fX); 630 } while (start == stop); 631 } 632 } 633 634 SkMatrix matrix; 635 636 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height())); 637 matrix.postTranslate(oval.centerX(), oval.centerY()); 638 639 return SkBuildQuadArc(start, stop, 640 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection, 641 &matrix, pts); 642} 643 644void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, 645 bool forceMoveTo) { 646 if (oval.width() < 0 || oval.height() < 0) { 647 return; 648 } 649 650 SkPoint pts[kSkBuildQuadArcStorage]; 651 int count = build_arc_points(oval, startAngle, sweepAngle, pts); 652 SkASSERT((count & 1) == 1); 653 654 if (fVerbs.count() == 0) { 655 forceMoveTo = true; 656 } 657 this->incReserve(count); 658 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]); 659 for (int i = 1; i < count; i += 2) { 660 this->quadTo(pts[i], pts[i+1]); 661 } 662} 663 664void SkPath::addArc(const SkRect& oval, SkScalar startAngle, 665 SkScalar sweepAngle) { 666 if (oval.isEmpty() || 0 == sweepAngle) { 667 return; 668 } 669 670 const SkScalar kFullCircleAngle = SkIntToScalar(360); 671 672 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) { 673 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction); 674 return; 675 } 676 677 SkPoint pts[kSkBuildQuadArcStorage]; 678 int count = build_arc_points(oval, startAngle, sweepAngle, pts); 679 680 this->incReserve(count); 681 this->moveTo(pts[0]); 682 for (int i = 1; i < count; i += 2) { 683 this->quadTo(pts[i], pts[i+1]); 684 } 685} 686 687/* 688 Need to handle the case when the angle is sharp, and our computed end-points 689 for the arc go behind pt1 and/or p2... 690*/ 691void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, 692 SkScalar radius) { 693 SkVector before, after; 694 695 // need to know our prev pt so we can construct tangent vectors 696 { 697 SkPoint start; 698 this->getLastPt(&start); 699 // Handle degenerate cases by adding a line to the first point and 700 // bailing out. 701 if ((x1 == start.fX && y1 == start.fY) || 702 (x1 == x2 && y1 == y2) || 703 radius == 0) { 704 this->lineTo(x1, y1); 705 return; 706 } 707 before.setNormalize(x1 - start.fX, y1 - start.fY); 708 after.setNormalize(x2 - x1, y2 - y1); 709 } 710 711 SkScalar cosh = SkPoint::DotProduct(before, after); 712 SkScalar sinh = SkPoint::CrossProduct(before, after); 713 714 if (SkScalarNearlyZero(sinh)) { // angle is too tight 715 this->lineTo(x1, y1); 716 return; 717 } 718 719 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh); 720 if (dist < 0) { 721 dist = -dist; 722 } 723 724 SkScalar xx = x1 - SkScalarMul(dist, before.fX); 725 SkScalar yy = y1 - SkScalarMul(dist, before.fY); 726 SkRotationDirection arcDir; 727 728 // now turn before/after into normals 729 if (sinh > 0) { 730 before.rotateCCW(); 731 after.rotateCCW(); 732 arcDir = kCW_SkRotationDirection; 733 } else { 734 before.rotateCW(); 735 after.rotateCW(); 736 arcDir = kCCW_SkRotationDirection; 737 } 738 739 SkMatrix matrix; 740 SkPoint pts[kSkBuildQuadArcStorage]; 741 742 matrix.setScale(radius, radius); 743 matrix.postTranslate(xx - SkScalarMul(radius, before.fX), 744 yy - SkScalarMul(radius, before.fY)); 745 746 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts); 747 748 this->incReserve(count); 749 // [xx,yy] == pts[0] 750 this->lineTo(xx, yy); 751 for (int i = 1; i < count; i += 2) { 752 this->quadTo(pts[i], pts[i+1]); 753 } 754} 755 756/////////////////////////////////////////////////////////////////////////////// 757 758void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) { 759 SkMatrix matrix; 760 761 matrix.setTranslate(dx, dy); 762 this->addPath(path, matrix); 763} 764 765void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) { 766 this->incReserve(path.fPts.count()); 767 768 Iter iter(path, false); 769 SkPoint pts[4]; 770 Verb verb; 771 772 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc(); 773 774 while ((verb = iter.next(pts)) != kDone_Verb) { 775 switch (verb) { 776 case kMove_Verb: 777 proc(matrix, &pts[0], &pts[0], 1); 778 this->moveTo(pts[0]); 779 break; 780 case kLine_Verb: 781 proc(matrix, &pts[1], &pts[1], 1); 782 this->lineTo(pts[1]); 783 break; 784 case kQuad_Verb: 785 proc(matrix, &pts[1], &pts[1], 2); 786 this->quadTo(pts[1], pts[2]); 787 break; 788 case kCubic_Verb: 789 proc(matrix, &pts[1], &pts[1], 3); 790 this->cubicTo(pts[1], pts[2], pts[3]); 791 break; 792 case kClose_Verb: 793 this->close(); 794 break; 795 default: 796 SkASSERT(!"unknown verb"); 797 } 798 } 799} 800 801/////////////////////////////////////////////////////////////////////////////// 802 803static const uint8_t gPtsInVerb[] = { 804 1, // kMove 805 1, // kLine 806 2, // kQuad 807 3, // kCubic 808 0, // kClose 809 0 // kDone 810}; 811 812// ignore the initial moveto, and stop when the 1st contour ends 813void SkPath::pathTo(const SkPath& path) { 814 int i, vcount = path.fVerbs.count(); 815 if (vcount == 0) { 816 return; 817 } 818 819 this->incReserve(vcount); 820 821 const uint8_t* verbs = path.fVerbs.begin(); 822 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo 823 824 SkASSERT(verbs[0] == kMove_Verb); 825 for (i = 1; i < vcount; i++) { 826 switch (verbs[i]) { 827 case kLine_Verb: 828 this->lineTo(pts[0].fX, pts[0].fY); 829 break; 830 case kQuad_Verb: 831 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY); 832 break; 833 case kCubic_Verb: 834 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, 835 pts[2].fX, pts[2].fY); 836 break; 837 case kClose_Verb: 838 return; 839 } 840 pts += gPtsInVerb[verbs[i]]; 841 } 842} 843 844// ignore the last point of the 1st contour 845void SkPath::reversePathTo(const SkPath& path) { 846 int i, vcount = path.fVerbs.count(); 847 if (vcount == 0) { 848 return; 849 } 850 851 this->incReserve(vcount); 852 853 const uint8_t* verbs = path.fVerbs.begin(); 854 const SkPoint* pts = path.fPts.begin(); 855 856 SkASSERT(verbs[0] == kMove_Verb); 857 for (i = 1; i < vcount; i++) { 858 int n = gPtsInVerb[verbs[i]]; 859 if (n == 0) { 860 break; 861 } 862 pts += n; 863 } 864 865 while (--i > 0) { 866 switch (verbs[i]) { 867 case kLine_Verb: 868 this->lineTo(pts[-1].fX, pts[-1].fY); 869 break; 870 case kQuad_Verb: 871 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY); 872 break; 873 case kCubic_Verb: 874 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY, 875 pts[-3].fX, pts[-3].fY); 876 break; 877 default: 878 SkASSERT(!"bad verb"); 879 break; 880 } 881 pts -= gPtsInVerb[verbs[i]]; 882 } 883} 884 885/////////////////////////////////////////////////////////////////////////////// 886 887void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const { 888 SkMatrix matrix; 889 890 matrix.setTranslate(dx, dy); 891 this->transform(matrix, dst); 892} 893 894#include "SkGeometry.h" 895 896static void subdivide_quad_to(SkPath* path, const SkPoint pts[3], 897 int level = 2) { 898 if (--level >= 0) { 899 SkPoint tmp[5]; 900 901 SkChopQuadAtHalf(pts, tmp); 902 subdivide_quad_to(path, &tmp[0], level); 903 subdivide_quad_to(path, &tmp[2], level); 904 } else { 905 path->quadTo(pts[1], pts[2]); 906 } 907} 908 909static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4], 910 int level = 2) { 911 if (--level >= 0) { 912 SkPoint tmp[7]; 913 914 SkChopCubicAtHalf(pts, tmp); 915 subdivide_cubic_to(path, &tmp[0], level); 916 subdivide_cubic_to(path, &tmp[3], level); 917 } else { 918 path->cubicTo(pts[1], pts[2], pts[3]); 919 } 920} 921 922void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const { 923 SkDEBUGCODE(this->validate();) 924 if (dst == NULL) { 925 dst = (SkPath*)this; 926 } 927 928 if (matrix.getType() & SkMatrix::kPerspective_Mask) { 929 SkPath tmp; 930 tmp.fFillType = fFillType; 931 932 SkPath::Iter iter(*this, false); 933 SkPoint pts[4]; 934 SkPath::Verb verb; 935 936 while ((verb = iter.next(pts)) != kDone_Verb) { 937 switch (verb) { 938 case kMove_Verb: 939 tmp.moveTo(pts[0]); 940 break; 941 case kLine_Verb: 942 tmp.lineTo(pts[1]); 943 break; 944 case kQuad_Verb: 945 subdivide_quad_to(&tmp, pts); 946 break; 947 case kCubic_Verb: 948 subdivide_cubic_to(&tmp, pts); 949 break; 950 case kClose_Verb: 951 tmp.close(); 952 break; 953 default: 954 SkASSERT(!"unknown verb"); 955 break; 956 } 957 } 958 959 dst->swap(tmp); 960 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count()); 961 } else { 962 // remember that dst might == this, so be sure to check 963 // fBoundsIsDirty before we set it 964 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) { 965 // if we're empty, fastbounds should not be mapped 966 matrix.mapRect(&dst->fBounds, fBounds); 967 dst->fBoundsIsDirty = false; 968 } else { 969 dst->fGenerationID++; 970 dst->fBoundsIsDirty = true; 971 } 972 973 if (this != dst) { 974 dst->fVerbs = fVerbs; 975 dst->fPts.setCount(fPts.count()); 976 dst->fFillType = fFillType; 977 } 978 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count()); 979 SkDEBUGCODE(dst->validate();) 980 } 981} 982 983/////////////////////////////////////////////////////////////////////////////// 984/////////////////////////////////////////////////////////////////////////////// 985 986enum NeedMoveToState { 987 kAfterClose_NeedMoveToState, 988 kAfterCons_NeedMoveToState, 989 kAfterPrefix_NeedMoveToState 990}; 991 992SkPath::Iter::Iter() { 993#ifdef SK_DEBUG 994 fPts = NULL; 995 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0; 996 fForceClose = fNeedMoveTo = fCloseLine = false; 997#endif 998 // need to init enough to make next() harmlessly return kDone_Verb 999 fVerbs = NULL; 1000 fVerbStop = NULL; 1001 fNeedClose = false; 1002} 1003 1004SkPath::Iter::Iter(const SkPath& path, bool forceClose) { 1005 this->setPath(path, forceClose); 1006} 1007 1008void SkPath::Iter::setPath(const SkPath& path, bool forceClose) { 1009 fPts = path.fPts.begin(); 1010 fVerbs = path.fVerbs.begin(); 1011 fVerbStop = path.fVerbs.end(); 1012 fForceClose = SkToU8(forceClose); 1013 fNeedClose = false; 1014 fNeedMoveTo = kAfterPrefix_NeedMoveToState; 1015} 1016 1017bool SkPath::Iter::isClosedContour() const { 1018 if (fVerbs == NULL || fVerbs == fVerbStop) { 1019 return false; 1020 } 1021 if (fForceClose) { 1022 return true; 1023 } 1024 1025 const uint8_t* verbs = fVerbs; 1026 const uint8_t* stop = fVerbStop; 1027 1028 if (kMove_Verb == *verbs) { 1029 verbs += 1; // skip the initial moveto 1030 } 1031 1032 while (verbs < stop) { 1033 unsigned v = *verbs++; 1034 if (kMove_Verb == v) { 1035 break; 1036 } 1037 if (kClose_Verb == v) { 1038 return true; 1039 } 1040 } 1041 return false; 1042} 1043 1044SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) { 1045 if (fLastPt != fMoveTo) { 1046 // A special case: if both points are NaN, SkPoint::operation== returns 1047 // false, but the iterator expects that they are treated as the same. 1048 // (consider SkPoint is a 2-dimension float point). 1049 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) || 1050 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) { 1051 return kClose_Verb; 1052 } 1053 1054 if (pts) { 1055 pts[0] = fLastPt; 1056 pts[1] = fMoveTo; 1057 } 1058 fLastPt = fMoveTo; 1059 fCloseLine = true; 1060 return kLine_Verb; 1061 } 1062 return kClose_Verb; 1063} 1064 1065bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) { 1066 if (fNeedMoveTo == kAfterClose_NeedMoveToState) { 1067 if (pts) { 1068 *pts = fMoveTo; 1069 } 1070 fNeedClose = fForceClose; 1071 fNeedMoveTo = kAfterCons_NeedMoveToState; 1072 fVerbs -= 1; 1073 return true; 1074 } 1075 1076 if (fNeedMoveTo == kAfterCons_NeedMoveToState) { 1077 if (pts) { 1078 *pts = fMoveTo; 1079 } 1080 fNeedMoveTo = kAfterPrefix_NeedMoveToState; 1081 } else { 1082 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState); 1083 if (pts) { 1084 *pts = fPts[-1]; 1085 } 1086 } 1087 return false; 1088} 1089 1090SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) { 1091 if (fVerbs == fVerbStop) { 1092 if (fNeedClose) { 1093 if (kLine_Verb == this->autoClose(pts)) { 1094 return kLine_Verb; 1095 } 1096 fNeedClose = false; 1097 return kClose_Verb; 1098 } 1099 return kDone_Verb; 1100 } 1101 1102 unsigned verb = *fVerbs++; 1103 const SkPoint* srcPts = fPts; 1104 1105 switch (verb) { 1106 case kMove_Verb: 1107 if (fNeedClose) { 1108 fVerbs -= 1; 1109 verb = this->autoClose(pts); 1110 if (verb == kClose_Verb) { 1111 fNeedClose = false; 1112 } 1113 return (Verb)verb; 1114 } 1115 if (fVerbs == fVerbStop) { // might be a trailing moveto 1116 return kDone_Verb; 1117 } 1118 fMoveTo = *srcPts; 1119 if (pts) { 1120 pts[0] = *srcPts; 1121 } 1122 srcPts += 1; 1123 fNeedMoveTo = kAfterCons_NeedMoveToState; 1124 fNeedClose = fForceClose; 1125 break; 1126 case kLine_Verb: 1127 if (this->cons_moveTo(pts)) { 1128 return kMove_Verb; 1129 } 1130 if (pts) { 1131 pts[1] = srcPts[0]; 1132 } 1133 fLastPt = srcPts[0]; 1134 fCloseLine = false; 1135 srcPts += 1; 1136 break; 1137 case kQuad_Verb: 1138 if (this->cons_moveTo(pts)) { 1139 return kMove_Verb; 1140 } 1141 if (pts) { 1142 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint)); 1143 } 1144 fLastPt = srcPts[1]; 1145 srcPts += 2; 1146 break; 1147 case kCubic_Verb: 1148 if (this->cons_moveTo(pts)) { 1149 return kMove_Verb; 1150 } 1151 if (pts) { 1152 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint)); 1153 } 1154 fLastPt = srcPts[2]; 1155 srcPts += 3; 1156 break; 1157 case kClose_Verb: 1158 verb = this->autoClose(pts); 1159 if (verb == kLine_Verb) { 1160 fVerbs -= 1; 1161 } else { 1162 fNeedClose = false; 1163 } 1164 fNeedMoveTo = kAfterClose_NeedMoveToState; 1165 break; 1166 } 1167 fPts = srcPts; 1168 return (Verb)verb; 1169} 1170 1171/////////////////////////////////////////////////////////////////////////////// 1172 1173static bool exceeds_dist(const SkScalar p[], const SkScalar q[], SkScalar dist, 1174 int count) { 1175 SkASSERT(dist > 0); 1176 1177 count *= 2; 1178 for (int i = 0; i < count; i++) { 1179 if (SkScalarAbs(p[i] - q[i]) > dist) { 1180 return true; 1181 } 1182 } 1183 return false; 1184} 1185 1186static void subdivide_quad(SkPath* dst, const SkPoint pts[3], SkScalar dist, 1187 int subLevel = 4) { 1188 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 4)) { 1189 SkPoint tmp[5]; 1190 SkChopQuadAtHalf(pts, tmp); 1191 1192 subdivide_quad(dst, &tmp[0], dist, subLevel); 1193 subdivide_quad(dst, &tmp[2], dist, subLevel); 1194 } else { 1195 dst->quadTo(pts[1], pts[2]); 1196 } 1197} 1198 1199static void subdivide_cubic(SkPath* dst, const SkPoint pts[4], SkScalar dist, 1200 int subLevel = 4) { 1201 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 6)) { 1202 SkPoint tmp[7]; 1203 SkChopCubicAtHalf(pts, tmp); 1204 1205 subdivide_cubic(dst, &tmp[0], dist, subLevel); 1206 subdivide_cubic(dst, &tmp[3], dist, subLevel); 1207 } else { 1208 dst->cubicTo(pts[1], pts[2], pts[3]); 1209 } 1210} 1211 1212void SkPath::subdivide(SkScalar dist, bool bendLines, SkPath* dst) const { 1213 SkPath tmpPath; 1214 if (NULL == dst || this == dst) { 1215 dst = &tmpPath; 1216 } 1217 1218 SkPath::Iter iter(*this, false); 1219 SkPoint pts[4]; 1220 1221 for (;;) { 1222 switch (iter.next(pts)) { 1223 case SkPath::kMove_Verb: 1224 dst->moveTo(pts[0]); 1225 break; 1226 case SkPath::kLine_Verb: 1227 if (!bendLines) { 1228 dst->lineTo(pts[1]); 1229 break; 1230 } 1231 // construct a quad from the line 1232 pts[2] = pts[1]; 1233 pts[1].set(SkScalarAve(pts[0].fX, pts[2].fX), 1234 SkScalarAve(pts[0].fY, pts[2].fY)); 1235 // fall through to the quad case 1236 case SkPath::kQuad_Verb: 1237 subdivide_quad(dst, pts, dist); 1238 break; 1239 case SkPath::kCubic_Verb: 1240 subdivide_cubic(dst, pts, dist); 1241 break; 1242 case SkPath::kClose_Verb: 1243 dst->close(); 1244 break; 1245 case SkPath::kDone_Verb: 1246 goto DONE; 1247 } 1248 } 1249DONE: 1250 if (&tmpPath == dst) { // i.e. the dst should be us 1251 dst->swap(*(SkPath*)this); 1252 } 1253} 1254 1255/////////////////////////////////////////////////////////////////////// 1256/* 1257 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]] 1258*/ 1259 1260void SkPath::flatten(SkFlattenableWriteBuffer& buffer) const { 1261 SkDEBUGCODE(this->validate();) 1262 1263 buffer.write32(fPts.count()); 1264 buffer.write32(fVerbs.count()); 1265 buffer.write32(fFillType); 1266 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count()); 1267 buffer.writePad(fVerbs.begin(), fVerbs.count()); 1268} 1269 1270void SkPath::unflatten(SkFlattenableReadBuffer& buffer) { 1271 fPts.setCount(buffer.readS32()); 1272 fVerbs.setCount(buffer.readS32()); 1273 fFillType = buffer.readS32(); 1274 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count()); 1275 buffer.read(fVerbs.begin(), fVerbs.count()); 1276 1277 fGenerationID++; 1278 fBoundsIsDirty = true; 1279 1280 SkDEBUGCODE(this->validate();) 1281} 1282 1283/////////////////////////////////////////////////////////////////////////////// 1284/////////////////////////////////////////////////////////////////////////////// 1285 1286void SkPath::dump(bool forceClose, const char title[]) const { 1287 Iter iter(*this, forceClose); 1288 SkPoint pts[4]; 1289 Verb verb; 1290 1291 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false", 1292 title ? title : ""); 1293 1294 while ((verb = iter.next(pts)) != kDone_Verb) { 1295 switch (verb) { 1296 case kMove_Verb: 1297#ifdef SK_CAN_USE_FLOAT 1298 SkDebugf(" path: moveTo [%g %g]\n", 1299 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY)); 1300#else 1301 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY); 1302#endif 1303 break; 1304 case kLine_Verb: 1305#ifdef SK_CAN_USE_FLOAT 1306 SkDebugf(" path: lineTo [%g %g]\n", 1307 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY)); 1308#else 1309 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY); 1310#endif 1311 break; 1312 case kQuad_Verb: 1313#ifdef SK_CAN_USE_FLOAT 1314 SkDebugf(" path: quadTo [%g %g] [%g %g]\n", 1315 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY), 1316 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY)); 1317#else 1318 SkDebugf(" path: quadTo [%x %x] [%x %x]\n", 1319 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY); 1320#endif 1321 break; 1322 case kCubic_Verb: 1323#ifdef SK_CAN_USE_FLOAT 1324 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n", 1325 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY), 1326 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY), 1327 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY)); 1328#else 1329 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n", 1330 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY, 1331 pts[3].fX, pts[3].fY); 1332#endif 1333 break; 1334 case kClose_Verb: 1335 SkDebugf(" path: close\n"); 1336 break; 1337 default: 1338 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb); 1339 verb = kDone_Verb; // stop the loop 1340 break; 1341 } 1342 } 1343 SkDebugf("path: done %s\n", title ? title : ""); 1344} 1345 1346void SkPath::dump() const { 1347 this->dump(false); 1348} 1349 1350#ifdef SK_DEBUG 1351void SkPath::validate() const { 1352 SkASSERT(this != NULL); 1353 SkASSERT((fFillType & ~3) == 0); 1354 fPts.validate(); 1355 fVerbs.validate(); 1356 1357 if (!fBoundsIsDirty) { 1358 SkRect bounds; 1359 compute_pt_bounds(&bounds, fPts); 1360 if (fPts.count() <= 1) { 1361 // if we're empty, fBounds may be empty but translated, so we can't 1362 // necessarily compare to bounds directly 1363 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will 1364 // be [2, 2, 2, 2] 1365 SkASSERT(bounds.isEmpty()); 1366 SkASSERT(fBounds.isEmpty()); 1367 } else { 1368 fBounds.contains(bounds); 1369 } 1370 } 1371} 1372#endif 1373 1374