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