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