blob: 634a6b1b593052ff05d5a4fbd4912e921b1ee599 [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);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000590
591 /* If the sweep angle is nearly (but less than) 360, then due to precision
592 loss in radians-conversion and/or sin/cos, we may end up with coincident
593 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
594 of drawing a nearly complete circle (good).
595 e.g. canvas.drawArc(0, 359.99, ...)
596 -vs- canvas.drawArc(0, 359.9, ...)
597 We try to detect this edge case, and tweak the stop vector
598 */
599 if (start == stop) {
600 SkScalar sw = SkScalarAbs(sweepAngle);
601 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
602 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
603 // make a guess at a tiny angle (in radians) to tweak by
604 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
605 // not sure how much will be enough, so we use a loop
606 do {
607 stopRad -= deltaRad;
608 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
609 } while (start == stop);
610 }
611 }
612
reed@android.com8a1c16f2008-12-17 15:59:43 +0000613 SkMatrix matrix;
614
615 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
616 matrix.postTranslate(oval.centerX(), oval.centerY());
617
618 return SkBuildQuadArc(start, stop,
619 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
620 &matrix, pts);
621}
622
623void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
624 bool forceMoveTo) {
625 if (oval.width() < 0 || oval.height() < 0) {
626 return;
627 }
628
629 SkPoint pts[kSkBuildQuadArcStorage];
630 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
631 SkASSERT((count & 1) == 1);
632
633 if (fVerbs.count() == 0) {
634 forceMoveTo = true;
635 }
636 this->incReserve(count);
637 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
638 for (int i = 1; i < count; i += 2) {
639 this->quadTo(pts[i], pts[i+1]);
640 }
641}
642
643void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
644 SkScalar sweepAngle) {
645 if (oval.isEmpty() || 0 == sweepAngle) {
646 return;
647 }
648
649 const SkScalar kFullCircleAngle = SkIntToScalar(360);
650
651 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
652 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
653 return;
654 }
655
656 SkPoint pts[kSkBuildQuadArcStorage];
657 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
658
659 this->incReserve(count);
660 this->moveTo(pts[0]);
661 for (int i = 1; i < count; i += 2) {
662 this->quadTo(pts[i], pts[i+1]);
663 }
664}
665
666/*
667 Need to handle the case when the angle is sharp, and our computed end-points
668 for the arc go behind pt1 and/or p2...
669*/
670void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
671 SkScalar radius) {
672 SkVector before, after;
673
674 // need to know our prev pt so we can construct tangent vectors
675 {
676 SkPoint start;
677 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000678 // Handle degenerate cases by adding a line to the first point and
679 // bailing out.
680 if ((x1 == start.fX && y1 == start.fY) ||
681 (x1 == x2 && y1 == y2) ||
682 radius == 0) {
683 this->lineTo(x1, y1);
684 return;
685 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 before.setNormalize(x1 - start.fX, y1 - start.fY);
687 after.setNormalize(x2 - x1, y2 - y1);
688 }
689
690 SkScalar cosh = SkPoint::DotProduct(before, after);
691 SkScalar sinh = SkPoint::CrossProduct(before, after);
692
693 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000694 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 return;
696 }
697
698 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
699 if (dist < 0) {
700 dist = -dist;
701 }
702
703 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
704 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
705 SkRotationDirection arcDir;
706
707 // now turn before/after into normals
708 if (sinh > 0) {
709 before.rotateCCW();
710 after.rotateCCW();
711 arcDir = kCW_SkRotationDirection;
712 } else {
713 before.rotateCW();
714 after.rotateCW();
715 arcDir = kCCW_SkRotationDirection;
716 }
717
718 SkMatrix matrix;
719 SkPoint pts[kSkBuildQuadArcStorage];
720
721 matrix.setScale(radius, radius);
722 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
723 yy - SkScalarMul(radius, before.fY));
724
725 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
726
727 this->incReserve(count);
728 // [xx,yy] == pts[0]
729 this->lineTo(xx, yy);
730 for (int i = 1; i < count; i += 2) {
731 this->quadTo(pts[i], pts[i+1]);
732 }
733}
734
735///////////////////////////////////////////////////////////////////////////////
736
737void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
738 SkMatrix matrix;
739
740 matrix.setTranslate(dx, dy);
741 this->addPath(path, matrix);
742}
743
744void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
745 this->incReserve(path.fPts.count());
746
747 Iter iter(path, false);
748 SkPoint pts[4];
749 Verb verb;
750
751 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
752
753 while ((verb = iter.next(pts)) != kDone_Verb) {
754 switch (verb) {
755 case kMove_Verb:
756 proc(matrix, &pts[0], &pts[0], 1);
757 this->moveTo(pts[0]);
758 break;
759 case kLine_Verb:
760 proc(matrix, &pts[1], &pts[1], 1);
761 this->lineTo(pts[1]);
762 break;
763 case kQuad_Verb:
764 proc(matrix, &pts[1], &pts[1], 2);
765 this->quadTo(pts[1], pts[2]);
766 break;
767 case kCubic_Verb:
768 proc(matrix, &pts[1], &pts[1], 3);
769 this->cubicTo(pts[1], pts[2], pts[3]);
770 break;
771 case kClose_Verb:
772 this->close();
773 break;
774 default:
775 SkASSERT(!"unknown verb");
776 }
777 }
778}
779
780///////////////////////////////////////////////////////////////////////////////
781
782static const uint8_t gPtsInVerb[] = {
783 1, // kMove
784 1, // kLine
785 2, // kQuad
786 3, // kCubic
787 0, // kClose
788 0 // kDone
789};
790
791// ignore the initial moveto, and stop when the 1st contour ends
792void SkPath::pathTo(const SkPath& path) {
793 int i, vcount = path.fVerbs.count();
794 if (vcount == 0) {
795 return;
796 }
797
798 this->incReserve(vcount);
799
800 const uint8_t* verbs = path.fVerbs.begin();
801 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
802
803 SkASSERT(verbs[0] == kMove_Verb);
804 for (i = 1; i < vcount; i++) {
805 switch (verbs[i]) {
806 case kLine_Verb:
807 this->lineTo(pts[0].fX, pts[0].fY);
808 break;
809 case kQuad_Verb:
810 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
811 break;
812 case kCubic_Verb:
813 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
814 pts[2].fX, pts[2].fY);
815 break;
816 case kClose_Verb:
817 return;
818 }
819 pts += gPtsInVerb[verbs[i]];
820 }
821}
822
823// ignore the last point of the 1st contour
824void SkPath::reversePathTo(const SkPath& path) {
825 int i, vcount = path.fVerbs.count();
826 if (vcount == 0) {
827 return;
828 }
829
830 this->incReserve(vcount);
831
832 const uint8_t* verbs = path.fVerbs.begin();
833 const SkPoint* pts = path.fPts.begin();
834
835 SkASSERT(verbs[0] == kMove_Verb);
836 for (i = 1; i < vcount; i++) {
837 int n = gPtsInVerb[verbs[i]];
838 if (n == 0) {
839 break;
840 }
841 pts += n;
842 }
843
844 while (--i > 0) {
845 switch (verbs[i]) {
846 case kLine_Verb:
847 this->lineTo(pts[-1].fX, pts[-1].fY);
848 break;
849 case kQuad_Verb:
850 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
851 break;
852 case kCubic_Verb:
853 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
854 pts[-3].fX, pts[-3].fY);
855 break;
856 default:
857 SkASSERT(!"bad verb");
858 break;
859 }
860 pts -= gPtsInVerb[verbs[i]];
861 }
862}
863
864///////////////////////////////////////////////////////////////////////////////
865
866void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
867 SkMatrix matrix;
868
869 matrix.setTranslate(dx, dy);
870 this->transform(matrix, dst);
871}
872
873#include "SkGeometry.h"
874
875static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
876 int level = 2) {
877 if (--level >= 0) {
878 SkPoint tmp[5];
879
880 SkChopQuadAtHalf(pts, tmp);
881 subdivide_quad_to(path, &tmp[0], level);
882 subdivide_quad_to(path, &tmp[2], level);
883 } else {
884 path->quadTo(pts[1], pts[2]);
885 }
886}
887
888static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
889 int level = 2) {
890 if (--level >= 0) {
891 SkPoint tmp[7];
892
893 SkChopCubicAtHalf(pts, tmp);
894 subdivide_cubic_to(path, &tmp[0], level);
895 subdivide_cubic_to(path, &tmp[3], level);
896 } else {
897 path->cubicTo(pts[1], pts[2], pts[3]);
898 }
899}
900
901void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
902 SkDEBUGCODE(this->validate();)
903 if (dst == NULL) {
904 dst = (SkPath*)this;
905 }
906
907 if (matrix.getType() & SkMatrix::kPerspective_Mask) {
908 SkPath tmp;
909 tmp.fFillType = fFillType;
910
911 SkPath::Iter iter(*this, false);
912 SkPoint pts[4];
913 SkPath::Verb verb;
914
915 while ((verb = iter.next(pts)) != kDone_Verb) {
916 switch (verb) {
917 case kMove_Verb:
918 tmp.moveTo(pts[0]);
919 break;
920 case kLine_Verb:
921 tmp.lineTo(pts[1]);
922 break;
923 case kQuad_Verb:
924 subdivide_quad_to(&tmp, pts);
925 break;
926 case kCubic_Verb:
927 subdivide_cubic_to(&tmp, pts);
928 break;
929 case kClose_Verb:
930 tmp.close();
931 break;
932 default:
933 SkASSERT(!"unknown verb");
934 break;
935 }
936 }
937
938 dst->swap(tmp);
939 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
940 } else {
941 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +0000942 // fBoundsIsDirty before we set it
943 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000944 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +0000945 matrix.mapRect(&dst->fBounds, fBounds);
946 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000947 } else {
reed@android.comd252db02009-04-01 18:31:44 +0000948 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000949 }
950
951 if (this != dst) {
952 dst->fVerbs = fVerbs;
953 dst->fPts.setCount(fPts.count());
954 dst->fFillType = fFillType;
955 }
956 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
957 SkDEBUGCODE(dst->validate();)
958 }
959}
960
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961///////////////////////////////////////////////////////////////////////////////
962///////////////////////////////////////////////////////////////////////////////
963
964enum NeedMoveToState {
965 kAfterClose_NeedMoveToState,
966 kAfterCons_NeedMoveToState,
967 kAfterPrefix_NeedMoveToState
968};
969
970SkPath::Iter::Iter() {
971#ifdef SK_DEBUG
972 fPts = NULL;
973 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
974 fForceClose = fNeedMoveTo = fCloseLine = false;
975#endif
976 // need to init enough to make next() harmlessly return kDone_Verb
977 fVerbs = NULL;
978 fVerbStop = NULL;
979 fNeedClose = false;
980}
981
982SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
983 this->setPath(path, forceClose);
984}
985
986void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
987 fPts = path.fPts.begin();
988 fVerbs = path.fVerbs.begin();
989 fVerbStop = path.fVerbs.end();
990 fForceClose = SkToU8(forceClose);
991 fNeedClose = false;
992 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
993}
994
995bool SkPath::Iter::isClosedContour() const {
996 if (fVerbs == NULL || fVerbs == fVerbStop) {
997 return false;
998 }
999 if (fForceClose) {
1000 return true;
1001 }
1002
1003 const uint8_t* verbs = fVerbs;
1004 const uint8_t* stop = fVerbStop;
1005
1006 if (kMove_Verb == *verbs) {
1007 verbs += 1; // skip the initial moveto
1008 }
1009
1010 while (verbs < stop) {
1011 unsigned v = *verbs++;
1012 if (kMove_Verb == v) {
1013 break;
1014 }
1015 if (kClose_Verb == v) {
1016 return true;
1017 }
1018 }
1019 return false;
1020}
1021
1022SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1023 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001024 // A special case: if both points are NaN, SkPoint::operation== returns
1025 // false, but the iterator expects that they are treated as the same.
1026 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001027 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1028 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001029 return kClose_Verb;
1030 }
1031
reed@android.com8a1c16f2008-12-17 15:59:43 +00001032 if (pts) {
1033 pts[0] = fLastPt;
1034 pts[1] = fMoveTo;
1035 }
1036 fLastPt = fMoveTo;
1037 fCloseLine = true;
1038 return kLine_Verb;
1039 }
1040 return kClose_Verb;
1041}
1042
1043bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
1044 if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
1045 if (pts) {
1046 *pts = fMoveTo;
1047 }
1048 fNeedClose = fForceClose;
1049 fNeedMoveTo = kAfterCons_NeedMoveToState;
1050 fVerbs -= 1;
1051 return true;
1052 }
1053
1054 if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
1055 if (pts) {
1056 *pts = fMoveTo;
1057 }
1058 fNeedMoveTo = kAfterPrefix_NeedMoveToState;
1059 } else {
1060 SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
1061 if (pts) {
1062 *pts = fPts[-1];
1063 }
1064 }
1065 return false;
1066}
1067
1068SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
1069 if (fVerbs == fVerbStop) {
1070 if (fNeedClose) {
1071 if (kLine_Verb == this->autoClose(pts)) {
1072 return kLine_Verb;
1073 }
1074 fNeedClose = false;
1075 return kClose_Verb;
1076 }
1077 return kDone_Verb;
1078 }
1079
1080 unsigned verb = *fVerbs++;
1081 const SkPoint* srcPts = fPts;
1082
1083 switch (verb) {
1084 case kMove_Verb:
1085 if (fNeedClose) {
1086 fVerbs -= 1;
1087 verb = this->autoClose(pts);
1088 if (verb == kClose_Verb) {
1089 fNeedClose = false;
1090 }
1091 return (Verb)verb;
1092 }
1093 if (fVerbs == fVerbStop) { // might be a trailing moveto
1094 return kDone_Verb;
1095 }
1096 fMoveTo = *srcPts;
1097 if (pts) {
1098 pts[0] = *srcPts;
1099 }
1100 srcPts += 1;
1101 fNeedMoveTo = kAfterCons_NeedMoveToState;
1102 fNeedClose = fForceClose;
1103 break;
1104 case kLine_Verb:
1105 if (this->cons_moveTo(pts)) {
1106 return kMove_Verb;
1107 }
1108 if (pts) {
1109 pts[1] = srcPts[0];
1110 }
1111 fLastPt = srcPts[0];
1112 fCloseLine = false;
1113 srcPts += 1;
1114 break;
1115 case kQuad_Verb:
1116 if (this->cons_moveTo(pts)) {
1117 return kMove_Verb;
1118 }
1119 if (pts) {
1120 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1121 }
1122 fLastPt = srcPts[1];
1123 srcPts += 2;
1124 break;
1125 case kCubic_Verb:
1126 if (this->cons_moveTo(pts)) {
1127 return kMove_Verb;
1128 }
1129 if (pts) {
1130 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1131 }
1132 fLastPt = srcPts[2];
1133 srcPts += 3;
1134 break;
1135 case kClose_Verb:
1136 verb = this->autoClose(pts);
1137 if (verb == kLine_Verb) {
1138 fVerbs -= 1;
1139 } else {
1140 fNeedClose = false;
1141 }
1142 fNeedMoveTo = kAfterClose_NeedMoveToState;
1143 break;
1144 }
1145 fPts = srcPts;
1146 return (Verb)verb;
1147}
1148
1149///////////////////////////////////////////////////////////////////////////////
1150
1151static bool exceeds_dist(const SkScalar p[], const SkScalar q[], SkScalar dist,
1152 int count) {
1153 SkASSERT(dist > 0);
1154
1155 count *= 2;
1156 for (int i = 0; i < count; i++) {
1157 if (SkScalarAbs(p[i] - q[i]) > dist) {
1158 return true;
1159 }
1160 }
1161 return false;
1162}
1163
1164static void subdivide_quad(SkPath* dst, const SkPoint pts[3], SkScalar dist,
1165 int subLevel = 4) {
1166 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 4)) {
1167 SkPoint tmp[5];
1168 SkChopQuadAtHalf(pts, tmp);
1169
1170 subdivide_quad(dst, &tmp[0], dist, subLevel);
1171 subdivide_quad(dst, &tmp[2], dist, subLevel);
1172 } else {
1173 dst->quadTo(pts[1], pts[2]);
1174 }
1175}
1176
1177static void subdivide_cubic(SkPath* dst, const SkPoint pts[4], SkScalar dist,
1178 int subLevel = 4) {
1179 if (--subLevel >= 0 && exceeds_dist(&pts[0].fX, &pts[1].fX, dist, 6)) {
1180 SkPoint tmp[7];
1181 SkChopCubicAtHalf(pts, tmp);
1182
1183 subdivide_cubic(dst, &tmp[0], dist, subLevel);
1184 subdivide_cubic(dst, &tmp[3], dist, subLevel);
1185 } else {
1186 dst->cubicTo(pts[1], pts[2], pts[3]);
1187 }
1188}
1189
1190void SkPath::subdivide(SkScalar dist, bool bendLines, SkPath* dst) const {
1191 SkPath tmpPath;
1192 if (NULL == dst || this == dst) {
1193 dst = &tmpPath;
1194 }
1195
1196 SkPath::Iter iter(*this, false);
1197 SkPoint pts[4];
1198
1199 for (;;) {
1200 switch (iter.next(pts)) {
1201 case SkPath::kMove_Verb:
1202 dst->moveTo(pts[0]);
1203 break;
1204 case SkPath::kLine_Verb:
1205 if (!bendLines) {
1206 dst->lineTo(pts[1]);
1207 break;
1208 }
1209 // construct a quad from the line
1210 pts[2] = pts[1];
1211 pts[1].set(SkScalarAve(pts[0].fX, pts[2].fX),
1212 SkScalarAve(pts[0].fY, pts[2].fY));
1213 // fall through to the quad case
1214 case SkPath::kQuad_Verb:
1215 subdivide_quad(dst, pts, dist);
1216 break;
1217 case SkPath::kCubic_Verb:
1218 subdivide_cubic(dst, pts, dist);
1219 break;
1220 case SkPath::kClose_Verb:
1221 dst->close();
1222 break;
1223 case SkPath::kDone_Verb:
1224 goto DONE;
1225 }
1226 }
1227DONE:
1228 if (&tmpPath == dst) { // i.e. the dst should be us
1229 dst->swap(*(SkPath*)this);
1230 }
1231}
1232
1233///////////////////////////////////////////////////////////////////////
1234/*
1235 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1236*/
1237
1238void SkPath::flatten(SkFlattenableWriteBuffer& buffer) const {
1239 SkDEBUGCODE(this->validate();)
1240
1241 buffer.write32(fPts.count());
1242 buffer.write32(fVerbs.count());
1243 buffer.write32(fFillType);
1244 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1245 buffer.writePad(fVerbs.begin(), fVerbs.count());
1246}
1247
1248void SkPath::unflatten(SkFlattenableReadBuffer& buffer) {
1249 fPts.setCount(buffer.readS32());
1250 fVerbs.setCount(buffer.readS32());
1251 fFillType = buffer.readS32();
1252 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1253 buffer.read(fVerbs.begin(), fVerbs.count());
1254
reed@android.comd252db02009-04-01 18:31:44 +00001255 fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256
1257 SkDEBUGCODE(this->validate();)
1258}
1259
1260///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261///////////////////////////////////////////////////////////////////////////////
1262
reed@android.com8a1c16f2008-12-17 15:59:43 +00001263void SkPath::dump(bool forceClose, const char title[]) const {
1264 Iter iter(*this, forceClose);
1265 SkPoint pts[4];
1266 Verb verb;
1267
1268 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1269 title ? title : "");
1270
1271 while ((verb = iter.next(pts)) != kDone_Verb) {
1272 switch (verb) {
1273 case kMove_Verb:
1274#ifdef SK_CAN_USE_FLOAT
1275 SkDebugf(" path: moveTo [%g %g]\n",
1276 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1277#else
1278 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1279#endif
1280 break;
1281 case kLine_Verb:
1282#ifdef SK_CAN_USE_FLOAT
1283 SkDebugf(" path: lineTo [%g %g]\n",
1284 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1285#else
1286 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1287#endif
1288 break;
1289 case kQuad_Verb:
1290#ifdef SK_CAN_USE_FLOAT
1291 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1292 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1293 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1294#else
1295 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1296 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1297#endif
1298 break;
1299 case kCubic_Verb:
1300#ifdef SK_CAN_USE_FLOAT
1301 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1302 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1303 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1304 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1305#else
1306 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1307 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1308 pts[3].fX, pts[3].fY);
1309#endif
1310 break;
1311 case kClose_Verb:
1312 SkDebugf(" path: close\n");
1313 break;
1314 default:
1315 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1316 verb = kDone_Verb; // stop the loop
1317 break;
1318 }
1319 }
1320 SkDebugf("path: done %s\n", title ? title : "");
1321}
1322
reed@android.come522ca52009-11-23 20:10:41 +00001323void SkPath::dump() const {
1324 this->dump(false);
1325}
1326
1327#ifdef SK_DEBUG
1328void SkPath::validate() const {
1329 SkASSERT(this != NULL);
1330 SkASSERT((fFillType & ~3) == 0);
1331 fPts.validate();
1332 fVerbs.validate();
1333
1334 if (!fBoundsIsDirty) {
1335 SkRect bounds;
1336 compute_pt_bounds(&bounds, fPts);
1337 if (fPts.count() <= 1) {
1338 // if we're empty, fBounds may be empty but translated, so we can't
1339 // necessarily compare to bounds directly
1340 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1341 // be [2, 2, 2, 2]
1342 SkASSERT(bounds.isEmpty());
1343 SkASSERT(fBounds.isEmpty());
1344 } else {
1345 fBounds.contains(bounds);
1346 }
1347 }
1348}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001349#endif
reed@android.come522ca52009-11-23 20:10:41 +00001350