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