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