blob: 1acc56b73ca1bdc2f0de796a5909a4ea25f1faa5 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkPath.h"
reed@google.com73945652011-04-25 19:04:27 +000011#include "SkReader32.h"
12#include "SkWriter32.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
14
15////////////////////////////////////////////////////////////////////////////
16
reed@google.com3563c9e2011-11-14 19:34:57 +000017/**
18 * Path.bounds is defined to be the bounds of all the control points.
19 * If we called bounds.join(r) we would skip r if r was empty, which breaks
20 * our promise. Hence we have a custom joiner that doesn't look at emptiness
21 */
22static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
23 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
24 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
25 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
26 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029/* This guy's constructor/destructor bracket a path editing operation. It is
30 used when we know the bounds of the amount we are going to add to the path
31 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000032
reed@android.com8a1c16f2008-12-17 15:59:43 +000033 It captures some state about the path up front (i.e. if it already has a
34 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000035 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000036
reed@android.com6b82d1a2009-06-03 02:35:01 +000037 It also notes if the path was originally empty, and if so, sets isConvex
38 to true. Thus it can only be used if the contour being added is convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 */
40class SkAutoPathBoundsUpdate {
41public:
42 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
43 this->init(path);
44 }
45
46 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
47 SkScalar right, SkScalar bottom) {
48 fRect.set(left, top, right, bottom);
49 this->init(path);
50 }
reed@google.comabf15c12011-01-18 20:35:51 +000051
reed@android.com8a1c16f2008-12-17 15:59:43 +000052 ~SkAutoPathBoundsUpdate() {
reed@android.com6b82d1a2009-06-03 02:35:01 +000053 fPath->setIsConvex(fEmpty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000055 fPath->fBounds = fRect;
56 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000058 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000059 fPath->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 }
61 }
reed@google.comabf15c12011-01-18 20:35:51 +000062
reed@android.com8a1c16f2008-12-17 15:59:43 +000063private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000064 SkPath* fPath;
65 SkRect fRect;
66 bool fDirty;
67 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000070 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000071 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +000072 fDirty = SkToBool(path->fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +000073 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +000074 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +000075 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 }
77};
78
reed@android.comd252db02009-04-01 18:31:44 +000079static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 if (pts.count() <= 1) { // we ignore just 1 point (moveto)
81 bounds->set(0, 0, 0, 0);
82 } else {
83 bounds->set(pts.begin(), pts.count());
84// SkDebugf("------- compute bounds %p %d", &pts, pts.count());
85 }
86}
87
88////////////////////////////////////////////////////////////////////////////
89
90/*
91 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +000092 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +000093 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
94
95 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +000096 1. If we encounter degenerate segments, remove them
97 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
98 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
99 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100*/
101
102////////////////////////////////////////////////////////////////////////////
103
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000104SkPath::SkPath()
105 : fFillType(kWinding_FillType)
106 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000107 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000108 fSegmentMask = 0;
djsollen@google.com56c69772011-11-08 19:00:26 +0000109#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000110 fGenerationID = 0;
111#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000112}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113
114SkPath::SkPath(const SkPath& src) {
115 SkDEBUGCODE(src.validate();)
116 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000117#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000118 // the assignment operator above increments the ID so correct for that here
119 fGenerationID--;
120#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121}
122
123SkPath::~SkPath() {
124 SkDEBUGCODE(this->validate();)
125}
126
127SkPath& SkPath::operator=(const SkPath& src) {
128 SkDEBUGCODE(src.validate();)
129
130 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000131 fBounds = src.fBounds;
132 fPts = src.fPts;
133 fVerbs = src.fVerbs;
134 fFillType = src.fFillType;
135 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000136 fConvexity = src.fConvexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000137 fSegmentMask = src.fSegmentMask;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000138 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 }
140 SkDEBUGCODE(this->validate();)
141 return *this;
142}
143
reed@android.com3abec1d2009-03-02 05:36:20 +0000144bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000145 // note: don't need to look at isConvex or bounds, since just comparing the
146 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000147
148 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
149 // since it is only a cache of info in the fVerbs, but its a fast way to
150 // notice a difference
151
reed@android.com3abec1d2009-03-02 05:36:20 +0000152 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000153 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
154 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000155}
156
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157void SkPath::swap(SkPath& other) {
158 SkASSERT(&other != NULL);
159
160 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000161 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 fPts.swap(other.fPts);
163 fVerbs.swap(other.fVerbs);
164 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000165 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000166 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000167 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000168 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 }
170}
171
djsollen@google.com56c69772011-11-08 19:00:26 +0000172#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000173uint32_t SkPath::getGenerationID() const {
174 return fGenerationID;
175}
176#endif
177
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178void SkPath::reset() {
179 SkDEBUGCODE(this->validate();)
180
181 fPts.reset();
182 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000183 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000184 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000185 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000186 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187}
188
189void SkPath::rewind() {
190 SkDEBUGCODE(this->validate();)
191
192 fPts.rewind();
193 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000194 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000195 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000196 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000197 fSegmentMask = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000198}
199
200bool SkPath::isEmpty() const {
201 SkDEBUGCODE(this->validate();)
schenney@chromium.orge42b1d52011-12-21 19:13:51 +0000202#if SK_OLD_EMPTY_PATH_BEHAVIOR
203 int count = fVerbs.count();
204 return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
205#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000206 return 0 == fVerbs.count();
schenney@chromium.orge42b1d52011-12-21 19:13:51 +0000207#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208}
209
caryclark@google.comf1316942011-07-26 19:54:45 +0000210/*
211 Determines if path is a rect by keeping track of changes in direction
212 and looking for a loop either clockwise or counterclockwise.
213
214 The direction is computed such that:
215 0: vertical up
216 1: horizontal right
217 2: vertical down
218 3: horizontal left
219
220A rectangle cycles up/right/down/left or up/left/down/right.
221
222The test fails if:
223 The path is closed, and followed by a line.
224 A second move creates a new endpoint.
225 A diagonal line is parsed.
226 There's more than four changes of direction.
227 There's a discontinuity on the line (e.g., a move in the middle)
228 The line reverses direction.
229 The rectangle doesn't complete a cycle.
230 The path contains a quadratic or cubic.
231 The path contains fewer than four points.
232 The final point isn't equal to the first point.
233
234It's OK if the path has:
235 Several colinear line segments composing a rectangle side.
236 Single points on the rectangle side.
237
238The direction takes advantage of the corners found since opposite sides
239must travel in opposite directions.
240
241FIXME: Allow colinear quads and cubics to be treated like lines.
242FIXME: If the API passes fill-only, return true if the filled stroke
243 is a rectangle, though the caller failed to close the path.
244 */
245bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000247
caryclark@google.comf1316942011-07-26 19:54:45 +0000248 int corners = 0;
249 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000250 first.set(0, 0);
251 last.set(0, 0);
252 int firstDirection = 0;
253 int lastDirection = 0;
254 int nextDirection = 0;
255 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000256 bool autoClose = false;
257 const uint8_t* verbs = fVerbs.begin();
258 const uint8_t* verbStop = fVerbs.end();
259 const SkPoint* pts = fPts.begin();
260 while (verbs != verbStop) {
261 switch (*verbs++) {
262 case kClose_Verb:
263 pts = fPts.begin();
264 autoClose = true;
265 case kLine_Verb: {
266 SkScalar left = last.fX;
267 SkScalar top = last.fY;
268 SkScalar right = pts->fX;
269 SkScalar bottom = pts->fY;
270 ++pts;
271 if (left != right && top != bottom) {
272 return false; // diagonal
273 }
274 if (left == right && top == bottom) {
275 break; // single point on side OK
276 }
277 nextDirection = (left != right) << 0 |
278 (left < right || top < bottom) << 1;
279 if (0 == corners) {
280 firstDirection = nextDirection;
281 first = last;
282 last = pts[-1];
283 corners = 1;
284 closedOrMoved = false;
285 break;
286 }
287 if (closedOrMoved) {
288 return false; // closed followed by a line
289 }
290 closedOrMoved = autoClose;
291 if (lastDirection != nextDirection) {
292 if (++corners > 4) {
293 return false; // too many direction changes
294 }
295 }
296 last = pts[-1];
297 if (lastDirection == nextDirection) {
298 break; // colinear segment
299 }
300 // Possible values for corners are 2, 3, and 4.
301 // When corners == 3, nextDirection opposes firstDirection.
302 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000303 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000304 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
305 if ((directionCycle ^ turn) != nextDirection) {
306 return false; // direction didn't follow cycle
307 }
308 break;
309 }
310 case kQuad_Verb:
311 case kCubic_Verb:
312 return false; // quadratic, cubic not allowed
313 case kMove_Verb:
314 last = *pts++;
315 closedOrMoved = true;
316 break;
317 }
318 lastDirection = nextDirection;
319 }
320 // Success if 4 corners and first point equals last
321 bool result = 4 == corners && first == last;
322 if (result && rect) {
323 *rect = getBounds();
324 }
325 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326}
327
328int SkPath::getPoints(SkPoint copy[], int max) const {
329 SkDEBUGCODE(this->validate();)
330
331 SkASSERT(max >= 0);
332 int count = fPts.count();
333 if (copy && max > 0 && count > 0) {
334 memcpy(copy, fPts.begin(), sizeof(SkPoint) * SkMin32(max, count));
335 }
336 return count;
337}
338
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000339SkPoint SkPath::getPoint(int index) const {
340 if ((unsigned)index < (unsigned)fPts.count()) {
341 return fPts[index];
342 }
343 return SkPoint::Make(0, 0);
344}
345
reed@google.com294dd7b2011-10-11 11:58:32 +0000346bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347 SkDEBUGCODE(this->validate();)
348
reed@google.com294dd7b2011-10-11 11:58:32 +0000349 int count = fPts.count();
350 if (count > 0) {
351 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000352 *lastPt = fPts[count - 1];
353 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000354 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000356 if (lastPt) {
357 lastPt->set(0, 0);
358 }
359 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360}
361
362void SkPath::setLastPt(SkScalar x, SkScalar y) {
363 SkDEBUGCODE(this->validate();)
364
365 int count = fPts.count();
366 if (count == 0) {
367 this->moveTo(x, y);
368 } else {
369 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000370 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000371 }
372}
373
reed@android.comd252db02009-04-01 18:31:44 +0000374void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000375 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000376 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000377
reed@android.comd252db02009-04-01 18:31:44 +0000378 fBoundsIsDirty = false;
379 compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000380}
381
reed@google.com04863fa2011-05-15 04:08:24 +0000382void SkPath::setConvexity(Convexity c) {
383 if (fConvexity != c) {
384 fConvexity = c;
385 GEN_ID_INC;
386 }
387}
388
reed@android.com8a1c16f2008-12-17 15:59:43 +0000389//////////////////////////////////////////////////////////////////////////////
390// Construction methods
391
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000392#define DIRTY_AFTER_EDIT \
393 do { \
394 fBoundsIsDirty = true; \
395 fConvexity = kUnknown_Convexity; \
reed@google.comb54455e2011-05-16 14:16:04 +0000396 } while (0)
397
reed@android.com8a1c16f2008-12-17 15:59:43 +0000398void SkPath::incReserve(U16CPU inc) {
399 SkDEBUGCODE(this->validate();)
400
401 fVerbs.setReserve(fVerbs.count() + inc);
402 fPts.setReserve(fPts.count() + inc);
403
404 SkDEBUGCODE(this->validate();)
405}
406
407void SkPath::moveTo(SkScalar x, SkScalar y) {
408 SkDEBUGCODE(this->validate();)
409
410 int vc = fVerbs.count();
411 SkPoint* pt;
412
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000413#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
414 if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
415 pt = &fPts[fPts.count() - 1];
416 } else {
417 pt = fPts.append();
418 *fVerbs.append() = kMove_Verb;
419 }
420#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000421 pt = fPts.append();
422 *fVerbs.append() = kMove_Verb;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000423#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000424 pt->set(x, y);
425
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000426 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000427 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000428}
429
430void SkPath::rMoveTo(SkScalar x, SkScalar y) {
431 SkPoint pt;
432 this->getLastPt(&pt);
433 this->moveTo(pt.fX + x, pt.fY + y);
434}
435
436void SkPath::lineTo(SkScalar x, SkScalar y) {
437 SkDEBUGCODE(this->validate();)
438
439 if (fVerbs.count() == 0) {
440 fPts.append()->set(0, 0);
441 *fVerbs.append() = kMove_Verb;
442 }
443 fPts.append()->set(x, y);
444 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000445 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000446
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000447 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000448 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449}
450
451void SkPath::rLineTo(SkScalar x, SkScalar y) {
452 SkPoint pt;
453 this->getLastPt(&pt);
454 this->lineTo(pt.fX + x, pt.fY + y);
455}
456
457void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
458 SkDEBUGCODE(this->validate();)
459
460 if (fVerbs.count() == 0) {
461 fPts.append()->set(0, 0);
462 *fVerbs.append() = kMove_Verb;
463 }
464
465 SkPoint* pts = fPts.append(2);
466 pts[0].set(x1, y1);
467 pts[1].set(x2, y2);
468 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000469 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000470
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000471 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000472 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000473}
474
475void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
476 SkPoint pt;
477 this->getLastPt(&pt);
478 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
479}
480
481void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
482 SkScalar x3, SkScalar y3) {
483 SkDEBUGCODE(this->validate();)
484
485 if (fVerbs.count() == 0) {
486 fPts.append()->set(0, 0);
487 *fVerbs.append() = kMove_Verb;
488 }
489 SkPoint* pts = fPts.append(3);
490 pts[0].set(x1, y1);
491 pts[1].set(x2, y2);
492 pts[2].set(x3, y3);
493 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000494 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000495
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000496 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000497 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000498}
499
500void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
501 SkScalar x3, SkScalar y3) {
502 SkPoint pt;
503 this->getLastPt(&pt);
504 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
505 pt.fX + x3, pt.fY + y3);
506}
507
508void SkPath::close() {
509 SkDEBUGCODE(this->validate();)
510
511 int count = fVerbs.count();
512 if (count > 0) {
513 switch (fVerbs[count - 1]) {
514 case kLine_Verb:
515 case kQuad_Verb:
516 case kCubic_Verb:
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000517#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000518 case kMove_Verb:
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +0000519#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000520 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000521 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000522 break;
523 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000524 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000525 break;
526 }
527 }
528}
529
530///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000531
reed@android.com8a1c16f2008-12-17 15:59:43 +0000532void SkPath::addRect(const SkRect& rect, Direction dir) {
533 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
534}
535
536void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
537 SkScalar bottom, Direction dir) {
538 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
539
540 this->incReserve(5);
541
542 this->moveTo(left, top);
543 if (dir == kCCW_Direction) {
544 this->lineTo(left, bottom);
545 this->lineTo(right, bottom);
546 this->lineTo(right, top);
547 } else {
548 this->lineTo(right, top);
549 this->lineTo(right, bottom);
550 this->lineTo(left, bottom);
551 }
552 this->close();
553}
554
555#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
556
557void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
558 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000559 SkScalar w = rect.width();
560 SkScalar halfW = SkScalarHalf(w);
561 SkScalar h = rect.height();
562 SkScalar halfH = SkScalarHalf(h);
563
564 if (halfW <= 0 || halfH <= 0) {
565 return;
566 }
567
reed@google.comabf15c12011-01-18 20:35:51 +0000568 bool skip_hori = rx >= halfW;
569 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570
571 if (skip_hori && skip_vert) {
572 this->addOval(rect, dir);
573 return;
574 }
reed@google.comabf15c12011-01-18 20:35:51 +0000575
576 SkAutoPathBoundsUpdate apbu(this, rect);
577
reed@android.com8a1c16f2008-12-17 15:59:43 +0000578 if (skip_hori) {
579 rx = halfW;
580 } else if (skip_vert) {
581 ry = halfH;
582 }
583
584 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
585 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
586
587 this->incReserve(17);
588 this->moveTo(rect.fRight - rx, rect.fTop);
589 if (dir == kCCW_Direction) {
590 if (!skip_hori) {
591 this->lineTo(rect.fLeft + rx, rect.fTop); // top
592 }
593 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
594 rect.fLeft, rect.fTop + ry - sy,
595 rect.fLeft, rect.fTop + ry); // top-left
596 if (!skip_vert) {
597 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
598 }
599 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
600 rect.fLeft + rx - sx, rect.fBottom,
601 rect.fLeft + rx, rect.fBottom); // bot-left
602 if (!skip_hori) {
603 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
604 }
605 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
606 rect.fRight, rect.fBottom - ry + sy,
607 rect.fRight, rect.fBottom - ry); // bot-right
608 if (!skip_vert) {
609 this->lineTo(rect.fRight, rect.fTop + ry);
610 }
611 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
612 rect.fRight - rx + sx, rect.fTop,
613 rect.fRight - rx, rect.fTop); // top-right
614 } else {
615 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
616 rect.fRight, rect.fTop + ry - sy,
617 rect.fRight, rect.fTop + ry); // top-right
618 if (!skip_vert) {
619 this->lineTo(rect.fRight, rect.fBottom - ry);
620 }
621 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
622 rect.fRight - rx + sx, rect.fBottom,
623 rect.fRight - rx, rect.fBottom); // bot-right
624 if (!skip_hori) {
625 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
626 }
627 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
628 rect.fLeft, rect.fBottom - ry + sy,
629 rect.fLeft, rect.fBottom - ry); // bot-left
630 if (!skip_vert) {
631 this->lineTo(rect.fLeft, rect.fTop + ry); // left
632 }
633 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
634 rect.fLeft + rx - sx, rect.fTop,
635 rect.fLeft + rx, rect.fTop); // top-left
636 if (!skip_hori) {
637 this->lineTo(rect.fRight - rx, rect.fTop); // top
638 }
639 }
640 this->close();
641}
642
643static void add_corner_arc(SkPath* path, const SkRect& rect,
644 SkScalar rx, SkScalar ry, int startAngle,
645 SkPath::Direction dir, bool forceMoveTo) {
646 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
647 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000648
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 SkRect r;
650 r.set(-rx, -ry, rx, ry);
651
652 switch (startAngle) {
653 case 0:
654 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
655 break;
656 case 90:
657 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
658 break;
659 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
660 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
661 default: SkASSERT(!"unexpected startAngle in add_corner_arc");
662 }
reed@google.comabf15c12011-01-18 20:35:51 +0000663
reed@android.com8a1c16f2008-12-17 15:59:43 +0000664 SkScalar start = SkIntToScalar(startAngle);
665 SkScalar sweep = SkIntToScalar(90);
666 if (SkPath::kCCW_Direction == dir) {
667 start += sweep;
668 sweep = -sweep;
669 }
reed@google.comabf15c12011-01-18 20:35:51 +0000670
reed@android.com8a1c16f2008-12-17 15:59:43 +0000671 path->arcTo(r, start, sweep, forceMoveTo);
672}
673
674void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
675 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000676 // abort before we invoke SkAutoPathBoundsUpdate()
677 if (rect.isEmpty()) {
678 return;
679 }
680
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681 SkAutoPathBoundsUpdate apbu(this, rect);
682
683 if (kCW_Direction == dir) {
684 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
685 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
686 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
687 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
688 } else {
689 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
690 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
691 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
692 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
693 }
694 this->close();
695}
696
697void SkPath::addOval(const SkRect& oval, Direction dir) {
698 SkAutoPathBoundsUpdate apbu(this, oval);
699
700 SkScalar cx = oval.centerX();
701 SkScalar cy = oval.centerY();
702 SkScalar rx = SkScalarHalf(oval.width());
703 SkScalar ry = SkScalarHalf(oval.height());
704#if 0 // these seem faster than using quads (1/2 the number of edges)
705 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
706 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
707
708 this->incReserve(13);
709 this->moveTo(cx + rx, cy);
710 if (dir == kCCW_Direction) {
711 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
712 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
713 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
714 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
715 } else {
716 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
717 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
718 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
719 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
720 }
721#else
722 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
723 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
724 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
725 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
726
727 /*
728 To handle imprecision in computing the center and radii, we revert to
729 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
730 to ensure that we don't exceed the oval's bounds *ever*, since we want
731 to use oval for our fast-bounds, rather than have to recompute it.
732 */
733 const SkScalar L = oval.fLeft; // cx - rx
734 const SkScalar T = oval.fTop; // cy - ry
735 const SkScalar R = oval.fRight; // cx + rx
736 const SkScalar B = oval.fBottom; // cy + ry
737
738 this->incReserve(17); // 8 quads + close
739 this->moveTo(R, cy);
740 if (dir == kCCW_Direction) {
741 this->quadTo( R, cy - sy, cx + mx, cy - my);
742 this->quadTo(cx + sx, T, cx , T);
743 this->quadTo(cx - sx, T, cx - mx, cy - my);
744 this->quadTo( L, cy - sy, L, cy );
745 this->quadTo( L, cy + sy, cx - mx, cy + my);
746 this->quadTo(cx - sx, B, cx , B);
747 this->quadTo(cx + sx, B, cx + mx, cy + my);
748 this->quadTo( R, cy + sy, R, cy );
749 } else {
750 this->quadTo( R, cy + sy, cx + mx, cy + my);
751 this->quadTo(cx + sx, B, cx , B);
752 this->quadTo(cx - sx, B, cx - mx, cy + my);
753 this->quadTo( L, cy + sy, L, cy );
754 this->quadTo( L, cy - sy, cx - mx, cy - my);
755 this->quadTo(cx - sx, T, cx , T);
756 this->quadTo(cx + sx, T, cx + mx, cy - my);
757 this->quadTo( R, cy - sy, R, cy );
758 }
759#endif
760 this->close();
761}
762
763void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
764 if (r > 0) {
765 SkRect rect;
766 rect.set(x - r, y - r, x + r, y + r);
767 this->addOval(rect, dir);
768 }
769}
770
771#include "SkGeometry.h"
772
773static int build_arc_points(const SkRect& oval, SkScalar startAngle,
774 SkScalar sweepAngle,
775 SkPoint pts[kSkBuildQuadArcStorage]) {
776 SkVector start, stop;
777
778 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
779 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
780 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000781
782 /* If the sweep angle is nearly (but less than) 360, then due to precision
783 loss in radians-conversion and/or sin/cos, we may end up with coincident
784 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
785 of drawing a nearly complete circle (good).
786 e.g. canvas.drawArc(0, 359.99, ...)
787 -vs- canvas.drawArc(0, 359.9, ...)
788 We try to detect this edge case, and tweak the stop vector
789 */
790 if (start == stop) {
791 SkScalar sw = SkScalarAbs(sweepAngle);
792 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
793 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
794 // make a guess at a tiny angle (in radians) to tweak by
795 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
796 // not sure how much will be enough, so we use a loop
797 do {
798 stopRad -= deltaRad;
799 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
800 } while (start == stop);
801 }
802 }
803
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000805
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
807 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000808
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 return SkBuildQuadArc(start, stop,
810 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
811 &matrix, pts);
812}
813
814void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
815 bool forceMoveTo) {
816 if (oval.width() < 0 || oval.height() < 0) {
817 return;
818 }
819
820 SkPoint pts[kSkBuildQuadArcStorage];
821 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
822 SkASSERT((count & 1) == 1);
823
824 if (fVerbs.count() == 0) {
825 forceMoveTo = true;
826 }
827 this->incReserve(count);
828 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
829 for (int i = 1; i < count; i += 2) {
830 this->quadTo(pts[i], pts[i+1]);
831 }
832}
833
834void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
835 SkScalar sweepAngle) {
836 if (oval.isEmpty() || 0 == sweepAngle) {
837 return;
838 }
839
840 const SkScalar kFullCircleAngle = SkIntToScalar(360);
841
842 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
843 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
844 return;
845 }
846
847 SkPoint pts[kSkBuildQuadArcStorage];
848 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
849
850 this->incReserve(count);
851 this->moveTo(pts[0]);
852 for (int i = 1; i < count; i += 2) {
853 this->quadTo(pts[i], pts[i+1]);
854 }
855}
856
857/*
858 Need to handle the case when the angle is sharp, and our computed end-points
859 for the arc go behind pt1 and/or p2...
860*/
861void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
862 SkScalar radius) {
863 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +0000864
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 // need to know our prev pt so we can construct tangent vectors
866 {
867 SkPoint start;
868 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000869 // Handle degenerate cases by adding a line to the first point and
870 // bailing out.
871 if ((x1 == start.fX && y1 == start.fY) ||
872 (x1 == x2 && y1 == y2) ||
873 radius == 0) {
874 this->lineTo(x1, y1);
875 return;
876 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 before.setNormalize(x1 - start.fX, y1 - start.fY);
878 after.setNormalize(x2 - x1, y2 - y1);
879 }
reed@google.comabf15c12011-01-18 20:35:51 +0000880
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 SkScalar cosh = SkPoint::DotProduct(before, after);
882 SkScalar sinh = SkPoint::CrossProduct(before, after);
883
884 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +0000885 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886 return;
887 }
reed@google.comabf15c12011-01-18 20:35:51 +0000888
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
890 if (dist < 0) {
891 dist = -dist;
892 }
893
894 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
895 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
896 SkRotationDirection arcDir;
897
898 // now turn before/after into normals
899 if (sinh > 0) {
900 before.rotateCCW();
901 after.rotateCCW();
902 arcDir = kCW_SkRotationDirection;
903 } else {
904 before.rotateCW();
905 after.rotateCW();
906 arcDir = kCCW_SkRotationDirection;
907 }
908
909 SkMatrix matrix;
910 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +0000911
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 matrix.setScale(radius, radius);
913 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
914 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +0000915
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +0000917
reed@android.com8a1c16f2008-12-17 15:59:43 +0000918 this->incReserve(count);
919 // [xx,yy] == pts[0]
920 this->lineTo(xx, yy);
921 for (int i = 1; i < count; i += 2) {
922 this->quadTo(pts[i], pts[i+1]);
923 }
924}
925
926///////////////////////////////////////////////////////////////////////////////
927
928void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
929 SkMatrix matrix;
930
931 matrix.setTranslate(dx, dy);
932 this->addPath(path, matrix);
933}
934
935void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
936 this->incReserve(path.fPts.count());
937
938 Iter iter(path, false);
939 SkPoint pts[4];
940 Verb verb;
941
942 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
943
944 while ((verb = iter.next(pts)) != kDone_Verb) {
945 switch (verb) {
946 case kMove_Verb:
947 proc(matrix, &pts[0], &pts[0], 1);
948 this->moveTo(pts[0]);
949 break;
950 case kLine_Verb:
951 proc(matrix, &pts[1], &pts[1], 1);
952 this->lineTo(pts[1]);
953 break;
954 case kQuad_Verb:
955 proc(matrix, &pts[1], &pts[1], 2);
956 this->quadTo(pts[1], pts[2]);
957 break;
958 case kCubic_Verb:
959 proc(matrix, &pts[1], &pts[1], 3);
960 this->cubicTo(pts[1], pts[2], pts[3]);
961 break;
962 case kClose_Verb:
963 this->close();
964 break;
965 default:
966 SkASSERT(!"unknown verb");
967 }
968 }
969}
970
971///////////////////////////////////////////////////////////////////////////////
972
973static const uint8_t gPtsInVerb[] = {
974 1, // kMove
975 1, // kLine
976 2, // kQuad
977 3, // kCubic
978 0, // kClose
979 0 // kDone
980};
981
982// ignore the initial moveto, and stop when the 1st contour ends
983void SkPath::pathTo(const SkPath& path) {
984 int i, vcount = path.fVerbs.count();
985 if (vcount == 0) {
986 return;
987 }
988
989 this->incReserve(vcount);
990
991 const uint8_t* verbs = path.fVerbs.begin();
992 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
993
994 SkASSERT(verbs[0] == kMove_Verb);
995 for (i = 1; i < vcount; i++) {
996 switch (verbs[i]) {
997 case kLine_Verb:
998 this->lineTo(pts[0].fX, pts[0].fY);
999 break;
1000 case kQuad_Verb:
1001 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1002 break;
1003 case kCubic_Verb:
1004 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1005 pts[2].fX, pts[2].fY);
1006 break;
1007 case kClose_Verb:
1008 return;
1009 }
1010 pts += gPtsInVerb[verbs[i]];
1011 }
1012}
1013
1014// ignore the last point of the 1st contour
1015void SkPath::reversePathTo(const SkPath& path) {
1016 int i, vcount = path.fVerbs.count();
1017 if (vcount == 0) {
1018 return;
1019 }
1020
1021 this->incReserve(vcount);
1022
1023 const uint8_t* verbs = path.fVerbs.begin();
1024 const SkPoint* pts = path.fPts.begin();
1025
1026 SkASSERT(verbs[0] == kMove_Verb);
1027 for (i = 1; i < vcount; i++) {
1028 int n = gPtsInVerb[verbs[i]];
1029 if (n == 0) {
1030 break;
1031 }
1032 pts += n;
1033 }
1034
1035 while (--i > 0) {
1036 switch (verbs[i]) {
1037 case kLine_Verb:
1038 this->lineTo(pts[-1].fX, pts[-1].fY);
1039 break;
1040 case kQuad_Verb:
1041 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1042 break;
1043 case kCubic_Verb:
1044 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1045 pts[-3].fX, pts[-3].fY);
1046 break;
1047 default:
1048 SkASSERT(!"bad verb");
1049 break;
1050 }
1051 pts -= gPtsInVerb[verbs[i]];
1052 }
1053}
1054
1055///////////////////////////////////////////////////////////////////////////////
1056
1057void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1058 SkMatrix matrix;
1059
1060 matrix.setTranslate(dx, dy);
1061 this->transform(matrix, dst);
1062}
1063
1064#include "SkGeometry.h"
1065
1066static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1067 int level = 2) {
1068 if (--level >= 0) {
1069 SkPoint tmp[5];
1070
1071 SkChopQuadAtHalf(pts, tmp);
1072 subdivide_quad_to(path, &tmp[0], level);
1073 subdivide_quad_to(path, &tmp[2], level);
1074 } else {
1075 path->quadTo(pts[1], pts[2]);
1076 }
1077}
1078
1079static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1080 int level = 2) {
1081 if (--level >= 0) {
1082 SkPoint tmp[7];
1083
1084 SkChopCubicAtHalf(pts, tmp);
1085 subdivide_cubic_to(path, &tmp[0], level);
1086 subdivide_cubic_to(path, &tmp[3], level);
1087 } else {
1088 path->cubicTo(pts[1], pts[2], pts[3]);
1089 }
1090}
1091
1092void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1093 SkDEBUGCODE(this->validate();)
1094 if (dst == NULL) {
1095 dst = (SkPath*)this;
1096 }
1097
tomhudson@google.com8d430182011-06-06 19:11:19 +00001098 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001099 SkPath tmp;
1100 tmp.fFillType = fFillType;
1101
1102 SkPath::Iter iter(*this, false);
1103 SkPoint pts[4];
1104 SkPath::Verb verb;
1105
1106 while ((verb = iter.next(pts)) != kDone_Verb) {
1107 switch (verb) {
1108 case kMove_Verb:
1109 tmp.moveTo(pts[0]);
1110 break;
1111 case kLine_Verb:
1112 tmp.lineTo(pts[1]);
1113 break;
1114 case kQuad_Verb:
1115 subdivide_quad_to(&tmp, pts);
1116 break;
1117 case kCubic_Verb:
1118 subdivide_cubic_to(&tmp, pts);
1119 break;
1120 case kClose_Verb:
1121 tmp.close();
1122 break;
1123 default:
1124 SkASSERT(!"unknown verb");
1125 break;
1126 }
1127 }
1128
1129 dst->swap(tmp);
1130 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1131 } else {
1132 // remember that dst might == this, so be sure to check
reed@android.comd252db02009-04-01 18:31:44 +00001133 // fBoundsIsDirty before we set it
1134 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001135 // if we're empty, fastbounds should not be mapped
reed@android.comd252db02009-04-01 18:31:44 +00001136 matrix.mapRect(&dst->fBounds, fBounds);
1137 dst->fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001138 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001139 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001140 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001141 }
1142
1143 if (this != dst) {
1144 dst->fVerbs = fVerbs;
1145 dst->fPts.setCount(fPts.count());
1146 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001147 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001148 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001149 }
1150 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
1151 SkDEBUGCODE(dst->validate();)
1152 }
1153}
1154
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155///////////////////////////////////////////////////////////////////////////////
1156///////////////////////////////////////////////////////////////////////////////
1157
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001158enum SegmentState {
1159 kAfterClose_SegmentState, // We will need a move next, but we have a
1160 // previous close pt to use for the new move.
1161 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1162 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1163 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001164};
1165
1166SkPath::Iter::Iter() {
1167#ifdef SK_DEBUG
1168 fPts = NULL;
1169 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001170 fForceClose = fCloseLine = false;
1171 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001172#endif
1173 // need to init enough to make next() harmlessly return kDone_Verb
1174 fVerbs = NULL;
1175 fVerbStop = NULL;
1176 fNeedClose = false;
1177}
1178
1179SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1180 this->setPath(path, forceClose);
1181}
1182
1183void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1184 fPts = path.fPts.begin();
1185 fVerbs = path.fVerbs.begin();
1186 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001187 fLastPt.fX = fLastPt.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 fForceClose = SkToU8(forceClose);
1189 fNeedClose = false;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001190 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191}
1192
1193bool SkPath::Iter::isClosedContour() const {
1194 if (fVerbs == NULL || fVerbs == fVerbStop) {
1195 return false;
1196 }
1197 if (fForceClose) {
1198 return true;
1199 }
1200
1201 const uint8_t* verbs = fVerbs;
1202 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001203
reed@android.com8a1c16f2008-12-17 15:59:43 +00001204 if (kMove_Verb == *verbs) {
1205 verbs += 1; // skip the initial moveto
1206 }
1207
1208 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001209 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210 if (kMove_Verb == v) {
1211 break;
1212 }
1213 if (kClose_Verb == v) {
1214 return true;
1215 }
1216 }
1217 return false;
1218}
1219
1220SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
1221 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001222 // A special case: if both points are NaN, SkPoint::operation== returns
1223 // false, but the iterator expects that they are treated as the same.
1224 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001225 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1226 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001227 return kClose_Verb;
1228 }
1229
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230 if (pts) {
1231 pts[0] = fLastPt;
1232 pts[1] = fMoveTo;
1233 }
1234 fLastPt = fMoveTo;
1235 fCloseLine = true;
1236 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001237 } else {
1238 pts[0] = fMoveTo;
1239 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241}
1242
1243bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001244 if (fSegmentState == kAfterClose_SegmentState) {
1245 // We have closed a curve and have a primitive, so we need a move.
1246 // Set the first return pt to the most recent move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001247 if (pts) {
1248 *pts = fMoveTo;
1249 }
1250 fNeedClose = fForceClose;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001251 fSegmentState = kAfterMove_SegmentState;
1252 fVerbs -= 1; // Step back to see the primitive again
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253 return true;
1254 }
1255
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001256 if (fSegmentState == kAfterMove_SegmentState) {
1257 // Set the first return pt to the move pt
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 if (pts) {
1259 *pts = fMoveTo;
1260 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001261 fSegmentState = kAfterPrimitive_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001263 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1264 // Set the first return pt to the last pt of the previous primitive.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265 if (pts) {
1266 *pts = fPts[-1];
1267 }
1268 }
1269 return false;
1270}
1271
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001272void SkPath::Iter::consumeDegenerateSegments() {
1273 // We need to step over anything that will not move the current draw point
1274 // forward before the next move is seen
1275 const uint8_t* lastMoveVerb = 0;
1276 const SkPoint* lastMovePt = 0;
1277 SkPoint lastPt = fLastPt;
1278 while (fVerbs != fVerbStop) {
1279 unsigned verb = *fVerbs;
1280 switch (verb) {
1281 case kMove_Verb:
1282 // Set state for the next method.
1283 fSegmentState = kAfterMove_SegmentState;
1284 fMoveTo = fPts[0];
1285
1286 // Keep a record of this most recent move
1287 lastMoveVerb = fVerbs;
1288 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001289 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001290 fVerbs++;
1291 fPts++;
1292 break;
1293
1294 case kClose_Verb:
1295 // A close when we are in a segment is always valid
1296 if (fSegmentState == kAfterPrimitive_SegmentState) {
1297 return;
1298 }
1299 // A close at any other time must be ignored
1300 fVerbs++;
1301 break;
1302
1303 case kLine_Verb:
1304 if (!IsLineDegenerate(lastPt, fPts[0])) {
1305 if (lastMoveVerb) {
1306 fVerbs = lastMoveVerb;
1307 fPts = lastMovePt;
1308 return;
1309 }
1310 return;
1311 }
1312 // Ignore this line and continue
1313 fVerbs++;
1314 fPts++;
1315 break;
1316
1317 case kQuad_Verb:
1318 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1319 if (lastMoveVerb) {
1320 fVerbs = lastMoveVerb;
1321 fPts = lastMovePt;
1322 return;
1323 }
1324 return;
1325 }
1326 // Ignore this line and continue
1327 fVerbs++;
1328 fPts += 2;
1329 break;
1330
1331 case kCubic_Verb:
1332 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1333 if (lastMoveVerb) {
1334 fVerbs = lastMoveVerb;
1335 fPts = lastMovePt;
1336 return;
1337 }
1338 return;
1339 }
1340 // Ignore this line and continue
1341 fVerbs++;
1342 fPts += 3;
1343 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001344
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001345 default:
1346 SkASSERT(false && "Should never see kDone_Verb");
1347 }
1348 }
1349}
1350
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001352#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001353 this->consumeDegenerateSegments();
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001354#endif
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001355
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001357 // Close the curve if requested and if there is some curve to close
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001358#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1359 if (fNeedClose) {
1360#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001361 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001362#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 if (kLine_Verb == this->autoClose(pts)) {
1364 return kLine_Verb;
1365 }
1366 fNeedClose = false;
1367 return kClose_Verb;
1368 }
1369 return kDone_Verb;
1370 }
1371
1372 unsigned verb = *fVerbs++;
1373 const SkPoint* srcPts = fPts;
1374
1375 switch (verb) {
1376 case kMove_Verb:
1377 if (fNeedClose) {
1378 fVerbs -= 1;
1379 verb = this->autoClose(pts);
1380 if (verb == kClose_Verb) {
1381 fNeedClose = false;
1382 }
1383 return (Verb)verb;
1384 }
1385 if (fVerbs == fVerbStop) { // might be a trailing moveto
1386 return kDone_Verb;
1387 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001388#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1389 fMoveTo = *srcPts;
1390#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 if (pts) {
1392 pts[0] = *srcPts;
1393 }
1394 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001395#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1396 fSegmentState = kAfterMove_SegmentState;
1397#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001398 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001399#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 fNeedClose = fForceClose;
1401 break;
1402 case kLine_Verb:
1403 if (this->cons_moveTo(pts)) {
1404 return kMove_Verb;
1405 }
1406 if (pts) {
1407 pts[1] = srcPts[0];
1408 }
1409 fLastPt = srcPts[0];
1410 fCloseLine = false;
1411 srcPts += 1;
1412 break;
1413 case kQuad_Verb:
1414 if (this->cons_moveTo(pts)) {
1415 return kMove_Verb;
1416 }
1417 if (pts) {
1418 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
1419 }
1420 fLastPt = srcPts[1];
1421 srcPts += 2;
1422 break;
1423 case kCubic_Verb:
1424 if (this->cons_moveTo(pts)) {
1425 return kMove_Verb;
1426 }
1427 if (pts) {
1428 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
1429 }
1430 fLastPt = srcPts[2];
1431 srcPts += 3;
1432 break;
1433 case kClose_Verb:
1434 verb = this->autoClose(pts);
1435 if (verb == kLine_Verb) {
1436 fVerbs -= 1;
1437 } else {
1438 fNeedClose = false;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001439#ifndef SK_OLD_EMPTY_PATH_BEHAVIOR
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001440 fSegmentState = kAfterClose_SegmentState;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001441#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001443#ifdef SK_OLD_EMPTY_PATH_BEHAVIOR
1444 fSegmentState = kAfterClose_SegmentState;
1445#else
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001446 fLastPt = fMoveTo;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001447#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 break;
1449 }
1450 fPts = srcPts;
1451 return (Verb)verb;
1452}
1453
1454///////////////////////////////////////////////////////////////////////////////
1455
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456/*
1457 Format in flattened buffer: [ptCount, verbCount, pts[], verbs[]]
1458*/
1459
reed@google.com73945652011-04-25 19:04:27 +00001460void SkPath::flatten(SkWriter32& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 SkDEBUGCODE(this->validate();)
1462
1463 buffer.write32(fPts.count());
1464 buffer.write32(fVerbs.count());
reed@google.com98b11f12011-09-21 18:40:27 +00001465 buffer.write32((fFillType << 8) | fSegmentMask);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 buffer.writeMul4(fPts.begin(), sizeof(SkPoint) * fPts.count());
1467 buffer.writePad(fVerbs.begin(), fVerbs.count());
1468}
1469
reed@google.com73945652011-04-25 19:04:27 +00001470void SkPath::unflatten(SkReader32& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 fPts.setCount(buffer.readS32());
1472 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001473 uint32_t packed = buffer.readS32();
1474 fFillType = packed >> 8;
1475 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1477 buffer.read(fVerbs.begin(), fVerbs.count());
reed@google.comabf15c12011-01-18 20:35:51 +00001478
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001479 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001480 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481
1482 SkDEBUGCODE(this->validate();)
1483}
1484
1485///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486///////////////////////////////////////////////////////////////////////////////
1487
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488void SkPath::dump(bool forceClose, const char title[]) const {
1489 Iter iter(*this, forceClose);
1490 SkPoint pts[4];
1491 Verb verb;
1492
1493 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1494 title ? title : "");
1495
1496 while ((verb = iter.next(pts)) != kDone_Verb) {
1497 switch (verb) {
1498 case kMove_Verb:
1499#ifdef SK_CAN_USE_FLOAT
1500 SkDebugf(" path: moveTo [%g %g]\n",
1501 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
1502#else
1503 SkDebugf(" path: moveTo [%x %x]\n", pts[0].fX, pts[0].fY);
1504#endif
1505 break;
1506 case kLine_Verb:
1507#ifdef SK_CAN_USE_FLOAT
1508 SkDebugf(" path: lineTo [%g %g]\n",
1509 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
1510#else
1511 SkDebugf(" path: lineTo [%x %x]\n", pts[1].fX, pts[1].fY);
1512#endif
1513 break;
1514 case kQuad_Verb:
1515#ifdef SK_CAN_USE_FLOAT
1516 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1517 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1518 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
1519#else
1520 SkDebugf(" path: quadTo [%x %x] [%x %x]\n",
1521 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
1522#endif
1523 break;
1524 case kCubic_Verb:
1525#ifdef SK_CAN_USE_FLOAT
1526 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1527 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1528 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1529 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
1530#else
1531 SkDebugf(" path: cubeTo [%x %x] [%x %x] [%x %x]\n",
1532 pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
1533 pts[3].fX, pts[3].fY);
1534#endif
1535 break;
1536 case kClose_Verb:
1537 SkDebugf(" path: close\n");
1538 break;
1539 default:
1540 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1541 verb = kDone_Verb; // stop the loop
1542 break;
1543 }
1544 }
1545 SkDebugf("path: done %s\n", title ? title : "");
1546}
1547
reed@android.come522ca52009-11-23 20:10:41 +00001548void SkPath::dump() const {
1549 this->dump(false);
1550}
1551
1552#ifdef SK_DEBUG
1553void SkPath::validate() const {
1554 SkASSERT(this != NULL);
1555 SkASSERT((fFillType & ~3) == 0);
1556 fPts.validate();
1557 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001558
reed@android.come522ca52009-11-23 20:10:41 +00001559 if (!fBoundsIsDirty) {
1560 SkRect bounds;
1561 compute_pt_bounds(&bounds, fPts);
1562 if (fPts.count() <= 1) {
1563 // if we're empty, fBounds may be empty but translated, so we can't
1564 // necessarily compare to bounds directly
1565 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1566 // be [2, 2, 2, 2]
1567 SkASSERT(bounds.isEmpty());
1568 SkASSERT(fBounds.isEmpty());
1569 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001570 if (bounds.isEmpty()) {
1571 SkASSERT(fBounds.isEmpty());
1572 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001573 if (!fBounds.isEmpty()) {
1574 SkASSERT(fBounds.contains(bounds));
1575 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001576 }
reed@android.come522ca52009-11-23 20:10:41 +00001577 }
1578 }
reed@google.com10296cc2011-09-21 12:29:05 +00001579
1580 uint32_t mask = 0;
1581 for (int i = 0; i < fVerbs.count(); i++) {
1582 switch (fVerbs[i]) {
1583 case kLine_Verb:
1584 mask |= kLine_SegmentMask;
1585 break;
1586 case kQuad_Verb:
1587 mask |= kQuad_SegmentMask;
1588 break;
1589 case kCubic_Verb:
1590 mask |= kCubic_SegmentMask;
1591 }
1592 }
1593 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001594}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595#endif
reed@android.come522ca52009-11-23 20:10:41 +00001596
reed@google.com04863fa2011-05-15 04:08:24 +00001597///////////////////////////////////////////////////////////////////////////////
1598
reed@google.com0b7b9822011-05-16 12:29:27 +00001599static int sign(SkScalar x) { return x < 0; }
1600#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001601
reed@google.com04863fa2011-05-15 04:08:24 +00001602static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001603 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001604}
1605
1606// only valid for a single contour
1607struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001608 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001609 fSign = 0;
1610 // warnings
1611 fCurrPt.set(0, 0);
1612 fVec0.set(0, 0);
1613 fVec1.set(0, 0);
1614 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001615
1616 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001617 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001618 }
1619
1620 SkPath::Convexity getConvexity() const { return fConvexity; }
1621
1622 void addPt(const SkPoint& pt) {
1623 if (SkPath::kConcave_Convexity == fConvexity) {
1624 return;
1625 }
1626
1627 if (0 == fPtCount) {
1628 fCurrPt = pt;
1629 ++fPtCount;
1630 } else {
1631 SkVector vec = pt - fCurrPt;
1632 if (vec.fX || vec.fY) {
1633 fCurrPt = pt;
1634 if (++fPtCount == 2) {
1635 fFirstVec = fVec1 = vec;
1636 } else {
1637 SkASSERT(fPtCount > 2);
1638 this->addVec(vec);
1639 }
reed@google.com85b6e392011-05-15 20:25:17 +00001640
1641 int sx = sign(vec.fX);
1642 int sy = sign(vec.fY);
1643 fDx += (sx != fSx);
1644 fDy += (sy != fSy);
1645 fSx = sx;
1646 fSy = sy;
1647
1648 if (fDx > 3 || fDy > 3) {
1649 fConvexity = SkPath::kConcave_Convexity;
1650 }
reed@google.com04863fa2011-05-15 04:08:24 +00001651 }
1652 }
1653 }
1654
1655 void close() {
1656 if (fPtCount > 2) {
1657 this->addVec(fFirstVec);
1658 }
1659 }
1660
1661private:
1662 void addVec(const SkVector& vec) {
1663 SkASSERT(vec.fX || vec.fY);
1664 fVec0 = fVec1;
1665 fVec1 = vec;
1666 int sign = CrossProductSign(fVec0, fVec1);
1667 if (0 == fSign) {
1668 fSign = sign;
1669 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001670 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001671 fConvexity = SkPath::kConcave_Convexity;
1672 }
1673 }
1674 }
1675
1676 SkPoint fCurrPt;
1677 SkVector fVec0, fVec1, fFirstVec;
1678 int fPtCount; // non-degenerate points
1679 int fSign;
1680 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001681 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001682};
1683
1684SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1685 SkPoint pts[4];
1686 SkPath::Verb verb;
1687 SkPath::Iter iter(path, true);
1688
1689 int contourCount = 0;
1690 int count;
1691 Convexicator state;
1692
1693 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1694 switch (verb) {
1695 case kMove_Verb:
1696 if (++contourCount > 1) {
1697 return kConcave_Convexity;
1698 }
1699 pts[1] = pts[0];
1700 count = 1;
1701 break;
1702 case kLine_Verb: count = 1; break;
1703 case kQuad_Verb: count = 2; break;
1704 case kCubic_Verb: count = 3; break;
1705 case kClose_Verb:
1706 state.close();
1707 count = 0;
1708 break;
1709 default:
1710 SkASSERT(!"bad verb");
1711 return kConcave_Convexity;
1712 }
1713
1714 for (int i = 1; i <= count; i++) {
1715 state.addPt(pts[i]);
1716 }
1717 // early exit
1718 if (kConcave_Convexity == state.getConvexity()) {
1719 return kConcave_Convexity;
1720 }
1721 }
1722 return state.getConvexity();
1723}