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