blob: 1127e956ea186e4f04dbeb08c3bd34ae1eec7aa8 [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"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
13
reed@google.com744faba2012-05-29 19:54:52 +000014// This value is just made-up for now. When count is 4, calling memset was much
15// slower than just writing the loop. This seems odd, and hopefully in the
16// future this we appear to have been a fluke...
17#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
18
reed@android.com8a1c16f2008-12-17 15:59:43 +000019////////////////////////////////////////////////////////////////////////////
20
reed@google.com3563c9e2011-11-14 19:34:57 +000021/**
22 * Path.bounds is defined to be the bounds of all the control points.
23 * If we called bounds.join(r) we would skip r if r was empty, which breaks
24 * our promise. Hence we have a custom joiner that doesn't look at emptiness
25 */
26static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
27 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
28 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
29 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
30 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
31}
32
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000033static bool is_degenerate(const SkPath& path) {
34 SkPath::Iter iter(path, false);
35 SkPoint pts[4];
36 return SkPath::kDone_Verb == iter.next(pts);
37}
38
bsalomon@google.com6aa29652012-04-18 13:29:52 +000039class SkAutoDisableOvalCheck {
40public:
41 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
42 fSaved = fPath->fIsOval;
43 }
44
45 ~SkAutoDisableOvalCheck() {
46 fPath->fIsOval = fSaved;
47 }
48
49private:
50 SkPath* fPath;
51 bool fSaved;
52};
53
reed@android.com8a1c16f2008-12-17 15:59:43 +000054/* This guy's constructor/destructor bracket a path editing operation. It is
55 used when we know the bounds of the amount we are going to add to the path
56 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000057
reed@android.com8a1c16f2008-12-17 15:59:43 +000058 It captures some state about the path up front (i.e. if it already has a
59 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000060 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000061
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000062 It also notes if the path was originally degenerate, and if so, sets
63 isConvex to true. Thus it can only be used if the contour being added is
64 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000065 */
66class SkAutoPathBoundsUpdate {
67public:
68 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
69 this->init(path);
70 }
71
72 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
73 SkScalar right, SkScalar bottom) {
74 fRect.set(left, top, right, bottom);
75 this->init(path);
76 }
reed@google.comabf15c12011-01-18 20:35:51 +000077
reed@android.com8a1c16f2008-12-17 15:59:43 +000078 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000079 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +000081 fPath->fBounds = fRect;
82 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +000083 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +000085 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +000086 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +000087 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 }
89 }
reed@google.comabf15c12011-01-18 20:35:51 +000090
reed@android.com8a1c16f2008-12-17 15:59:43 +000091private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 SkPath* fPath;
93 SkRect fRect;
94 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000095 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000096 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000097
reed@android.com8a1c16f2008-12-17 15:59:43 +000098 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +000099 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000100 fPath = path;
reed@android.com63debae2009-12-16 17:25:43 +0000101 fDirty = SkToBool(path->fBoundsIsDirty);
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000102 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000104 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000105 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106 }
107};
108
reed@google.com0bb18bb2012-07-26 15:20:36 +0000109// Return true if the computed bounds are finite.
110static bool compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
111 int count = pts.count();
112 if (count <= 1) { // we ignore just 1 point (moveto)
113 bounds->setEmpty();
114 return count ? pts.begin()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000115 } else {
reed@google.com0bb18bb2012-07-26 15:20:36 +0000116 return bounds->setBoundsCheck(pts.begin(), pts.count());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 }
118}
119
120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
reed@google.comd335d1d2012-01-12 18:17:11 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000139SkPath::SkPath()
140 : fFillType(kWinding_FillType)
141 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000142 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000143 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000144 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000145 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000146 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000147#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000148 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000149 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000150#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000151}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152
153SkPath::SkPath(const SkPath& src) {
154 SkDEBUGCODE(src.validate();)
155 *this = src;
djsollen@google.com56c69772011-11-08 19:00:26 +0000156#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000157 // the assignment operator above increments the ID so correct for that here
djsollen@google.come63793a2012-03-21 15:39:03 +0000158 fGenerationID = src.fGenerationID;
159 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000160#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
167SkPath& SkPath::operator=(const SkPath& src) {
168 SkDEBUGCODE(src.validate();)
169
170 if (this != &src) {
reed@android.comd252db02009-04-01 18:31:44 +0000171 fBounds = src.fBounds;
172 fPts = src.fPts;
173 fVerbs = src.fVerbs;
174 fFillType = src.fFillType;
175 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000176 fConvexity = src.fConvexity;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000177 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000178 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000179 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000180 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000181 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 }
183 SkDEBUGCODE(this->validate();)
184 return *this;
185}
186
reed@android.com3abec1d2009-03-02 05:36:20 +0000187bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000188 // note: don't need to look at isConvex or bounds, since just comparing the
189 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000190
191 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
192 // since it is only a cache of info in the fVerbs, but its a fast way to
193 // notice a difference
194
reed@android.com3abec1d2009-03-02 05:36:20 +0000195 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000196 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
197 a.fVerbs == b.fVerbs && a.fPts == b.fPts);
reed@android.com3abec1d2009-03-02 05:36:20 +0000198}
199
reed@android.com8a1c16f2008-12-17 15:59:43 +0000200void SkPath::swap(SkPath& other) {
201 SkASSERT(&other != NULL);
202
203 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000204 SkTSwap<SkRect>(fBounds, other.fBounds);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 fPts.swap(other.fPts);
206 fVerbs.swap(other.fVerbs);
207 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000208 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000209 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
reed@google.com10296cc2011-09-21 12:29:05 +0000210 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000211 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000212 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000213 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000214 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215 }
216}
217
djsollen@google.com56c69772011-11-08 19:00:26 +0000218#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000219uint32_t SkPath::getGenerationID() const {
220 return fGenerationID;
221}
djsollen@google.come63793a2012-03-21 15:39:03 +0000222
223const SkPath* SkPath::getSourcePath() const {
224 return fSourcePath;
225}
226
227void SkPath::setSourcePath(const SkPath* path) {
228 fSourcePath = path;
229}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000230#endif
231
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232void SkPath::reset() {
233 SkDEBUGCODE(this->validate();)
234
235 fPts.reset();
236 fVerbs.reset();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000237 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000238 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000239 fConvexity = kUnknown_Convexity;
reed@google.com10296cc2011-09-21 12:29:05 +0000240 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000241 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000242 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243}
244
245void SkPath::rewind() {
246 SkDEBUGCODE(this->validate();)
247
248 fPts.rewind();
249 fVerbs.rewind();
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000250 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000251 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000252 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000253 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000254 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000255 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256}
257
258bool SkPath::isEmpty() const {
259 SkDEBUGCODE(this->validate();)
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000260 return 0 == fVerbs.count();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000261}
262
reed@google.com7e6c4d12012-05-10 14:05:43 +0000263bool SkPath::isLine(SkPoint line[2]) const {
264 int verbCount = fVerbs.count();
265 int ptCount = fPts.count();
266
267 if (2 == verbCount && 2 == ptCount) {
268 const uint8_t* verbs = fVerbs.begin();
269 if (kMove_Verb == verbs[0] && kLine_Verb == verbs[1]) {
270 if (line) {
271 const SkPoint* pts = fPts.begin();
272 line[0] = pts[0];
273 line[1] = pts[1];
274 }
275 return true;
276 }
277 }
278 return false;
279}
280
caryclark@google.comf1316942011-07-26 19:54:45 +0000281/*
282 Determines if path is a rect by keeping track of changes in direction
283 and looking for a loop either clockwise or counterclockwise.
284
285 The direction is computed such that:
286 0: vertical up
287 1: horizontal right
288 2: vertical down
289 3: horizontal left
290
291A rectangle cycles up/right/down/left or up/left/down/right.
292
293The test fails if:
294 The path is closed, and followed by a line.
295 A second move creates a new endpoint.
296 A diagonal line is parsed.
297 There's more than four changes of direction.
298 There's a discontinuity on the line (e.g., a move in the middle)
299 The line reverses direction.
300 The rectangle doesn't complete a cycle.
301 The path contains a quadratic or cubic.
302 The path contains fewer than four points.
303 The final point isn't equal to the first point.
304
305It's OK if the path has:
306 Several colinear line segments composing a rectangle side.
307 Single points on the rectangle side.
308
309The direction takes advantage of the corners found since opposite sides
310must travel in opposite directions.
311
312FIXME: Allow colinear quads and cubics to be treated like lines.
313FIXME: If the API passes fill-only, return true if the filled stroke
314 is a rectangle, though the caller failed to close the path.
315 */
316bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000318
caryclark@google.comf1316942011-07-26 19:54:45 +0000319 int corners = 0;
320 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000321 first.set(0, 0);
322 last.set(0, 0);
323 int firstDirection = 0;
324 int lastDirection = 0;
325 int nextDirection = 0;
326 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000327 bool autoClose = false;
328 const uint8_t* verbs = fVerbs.begin();
329 const uint8_t* verbStop = fVerbs.end();
330 const SkPoint* pts = fPts.begin();
331 while (verbs != verbStop) {
332 switch (*verbs++) {
333 case kClose_Verb:
334 pts = fPts.begin();
335 autoClose = true;
336 case kLine_Verb: {
337 SkScalar left = last.fX;
338 SkScalar top = last.fY;
339 SkScalar right = pts->fX;
340 SkScalar bottom = pts->fY;
341 ++pts;
342 if (left != right && top != bottom) {
343 return false; // diagonal
344 }
345 if (left == right && top == bottom) {
346 break; // single point on side OK
347 }
348 nextDirection = (left != right) << 0 |
349 (left < right || top < bottom) << 1;
350 if (0 == corners) {
351 firstDirection = nextDirection;
352 first = last;
353 last = pts[-1];
354 corners = 1;
355 closedOrMoved = false;
356 break;
357 }
358 if (closedOrMoved) {
359 return false; // closed followed by a line
360 }
361 closedOrMoved = autoClose;
362 if (lastDirection != nextDirection) {
363 if (++corners > 4) {
364 return false; // too many direction changes
365 }
366 }
367 last = pts[-1];
368 if (lastDirection == nextDirection) {
369 break; // colinear segment
370 }
371 // Possible values for corners are 2, 3, and 4.
372 // When corners == 3, nextDirection opposes firstDirection.
373 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000374 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000375 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
376 if ((directionCycle ^ turn) != nextDirection) {
377 return false; // direction didn't follow cycle
378 }
379 break;
380 }
381 case kQuad_Verb:
382 case kCubic_Verb:
383 return false; // quadratic, cubic not allowed
384 case kMove_Verb:
385 last = *pts++;
386 closedOrMoved = true;
387 break;
388 }
389 lastDirection = nextDirection;
390 }
391 // Success if 4 corners and first point equals last
392 bool result = 4 == corners && first == last;
393 if (result && rect) {
394 *rect = getBounds();
395 }
396 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000397}
398
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000399int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000400 SkDEBUGCODE(this->validate();)
401
402 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000403 SkASSERT(!max || dst);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000404 int count = fPts.count();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000405 fPts.copyRange(dst, 0, max);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000406 return count;
407}
408
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000409SkPoint SkPath::getPoint(int index) const {
410 if ((unsigned)index < (unsigned)fPts.count()) {
411 return fPts[index];
412 }
413 return SkPoint::Make(0, 0);
414}
415
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000416int SkPath::getVerbs(uint8_t dst[], int max) const {
417 SkDEBUGCODE(this->validate();)
418
419 SkASSERT(max >= 0);
420 SkASSERT(!max || dst);
421 fVerbs.copyRange(dst, 0, max);
422 return fVerbs.count();
423}
424
reed@google.com294dd7b2011-10-11 11:58:32 +0000425bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000426 SkDEBUGCODE(this->validate();)
427
reed@google.com294dd7b2011-10-11 11:58:32 +0000428 int count = fPts.count();
429 if (count > 0) {
430 if (lastPt) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431 *lastPt = fPts[count - 1];
432 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000433 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000434 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000435 if (lastPt) {
436 lastPt->set(0, 0);
437 }
438 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000439}
440
441void SkPath::setLastPt(SkScalar x, SkScalar y) {
442 SkDEBUGCODE(this->validate();)
443
444 int count = fPts.count();
445 if (count == 0) {
446 this->moveTo(x, y);
447 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000448 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000449 fPts[count - 1].set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000450 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000451 }
452}
453
reed@android.comd252db02009-04-01 18:31:44 +0000454void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000455 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000456 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000457
reed@android.comd252db02009-04-01 18:31:44 +0000458 fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000459 fIsFinite = compute_pt_bounds(&fBounds, fPts);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000460}
461
reed@google.com04863fa2011-05-15 04:08:24 +0000462void SkPath::setConvexity(Convexity c) {
463 if (fConvexity != c) {
464 fConvexity = c;
465 GEN_ID_INC;
466 }
467}
468
reed@android.com8a1c16f2008-12-17 15:59:43 +0000469//////////////////////////////////////////////////////////////////////////////
470// Construction methods
471
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000472#define DIRTY_AFTER_EDIT \
473 do { \
474 fBoundsIsDirty = true; \
475 fConvexity = kUnknown_Convexity; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000476 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000477 } while (0)
478
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000479#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
480 do { \
481 fBoundsIsDirty = true; \
482 } while (0)
483
reed@android.com8a1c16f2008-12-17 15:59:43 +0000484void SkPath::incReserve(U16CPU inc) {
485 SkDEBUGCODE(this->validate();)
486
487 fVerbs.setReserve(fVerbs.count() + inc);
488 fPts.setReserve(fPts.count() + inc);
489
490 SkDEBUGCODE(this->validate();)
491}
492
493void SkPath::moveTo(SkScalar x, SkScalar y) {
494 SkDEBUGCODE(this->validate();)
495
reed@android.com8a1c16f2008-12-17 15:59:43 +0000496 SkPoint* pt;
497
reed@google.comd335d1d2012-01-12 18:17:11 +0000498 // remember our index
499 fLastMoveToIndex = fPts.count();
500
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000501 pt = fPts.append();
502 *fVerbs.append() = kMove_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000503 pt->set(x, y);
504
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000505 GEN_ID_INC;
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000506 DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000507}
508
509void SkPath::rMoveTo(SkScalar x, SkScalar y) {
510 SkPoint pt;
511 this->getLastPt(&pt);
512 this->moveTo(pt.fX + x, pt.fY + y);
513}
514
reed@google.comd335d1d2012-01-12 18:17:11 +0000515void SkPath::injectMoveToIfNeeded() {
516 if (fLastMoveToIndex < 0) {
517 SkScalar x, y;
518 if (fVerbs.count() == 0) {
519 x = y = 0;
520 } else {
521 const SkPoint& pt = fPts[~fLastMoveToIndex];
522 x = pt.fX;
523 y = pt.fY;
524 }
525 this->moveTo(x, y);
526 }
527}
528
reed@android.com8a1c16f2008-12-17 15:59:43 +0000529void SkPath::lineTo(SkScalar x, SkScalar y) {
530 SkDEBUGCODE(this->validate();)
531
reed@google.comd335d1d2012-01-12 18:17:11 +0000532 this->injectMoveToIfNeeded();
533
reed@android.com8a1c16f2008-12-17 15:59:43 +0000534 fPts.append()->set(x, y);
535 *fVerbs.append() = kLine_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000536 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000537
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000538 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000539 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540}
541
542void SkPath::rLineTo(SkScalar x, SkScalar y) {
543 SkPoint pt;
544 this->getLastPt(&pt);
545 this->lineTo(pt.fX + x, pt.fY + y);
546}
547
548void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
549 SkDEBUGCODE(this->validate();)
550
reed@google.comd335d1d2012-01-12 18:17:11 +0000551 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000552
553 SkPoint* pts = fPts.append(2);
554 pts[0].set(x1, y1);
555 pts[1].set(x2, y2);
556 *fVerbs.append() = kQuad_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000557 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000558
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000559 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000560 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000561}
562
563void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
564 SkPoint pt;
565 this->getLastPt(&pt);
566 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
567}
568
569void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
570 SkScalar x3, SkScalar y3) {
571 SkDEBUGCODE(this->validate();)
572
reed@google.comd335d1d2012-01-12 18:17:11 +0000573 this->injectMoveToIfNeeded();
574
reed@android.com8a1c16f2008-12-17 15:59:43 +0000575 SkPoint* pts = fPts.append(3);
576 pts[0].set(x1, y1);
577 pts[1].set(x2, y2);
578 pts[2].set(x3, y3);
579 *fVerbs.append() = kCubic_Verb;
reed@google.com10296cc2011-09-21 12:29:05 +0000580 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000582 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000583 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000584}
585
586void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
587 SkScalar x3, SkScalar y3) {
588 SkPoint pt;
589 this->getLastPt(&pt);
590 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
591 pt.fX + x3, pt.fY + y3);
592}
593
594void SkPath::close() {
595 SkDEBUGCODE(this->validate();)
596
597 int count = fVerbs.count();
598 if (count > 0) {
599 switch (fVerbs[count - 1]) {
600 case kLine_Verb:
601 case kQuad_Verb:
602 case kCubic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000603 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000604 *fVerbs.append() = kClose_Verb;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000605 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000606 break;
607 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000608 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 break;
610 }
611 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000612
613 // signal that we need a moveTo to follow us (unless we're done)
614#if 0
615 if (fLastMoveToIndex >= 0) {
616 fLastMoveToIndex = ~fLastMoveToIndex;
617 }
618#else
619 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
620#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621}
622
623///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000624
reed@android.com8a1c16f2008-12-17 15:59:43 +0000625void SkPath::addRect(const SkRect& rect, Direction dir) {
626 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
627}
628
629void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
630 SkScalar bottom, Direction dir) {
631 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
632
633 this->incReserve(5);
634
635 this->moveTo(left, top);
636 if (dir == kCCW_Direction) {
637 this->lineTo(left, bottom);
638 this->lineTo(right, bottom);
639 this->lineTo(right, top);
640 } else {
641 this->lineTo(right, top);
642 this->lineTo(right, bottom);
643 this->lineTo(left, bottom);
644 }
645 this->close();
646}
647
reed@google.com744faba2012-05-29 19:54:52 +0000648void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
649 SkDEBUGCODE(this->validate();)
650 if (count <= 0) {
651 return;
652 }
653
654 fLastMoveToIndex = fPts.count();
655 fPts.append(count, pts);
656
657 // +close makes room for the extra kClose_Verb
658 uint8_t* vb = fVerbs.append(count + close);
659 vb[0] = kMove_Verb;
660
661 if (count > 1) {
662 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
663 // be 0, the compiler will remove the test/branch entirely.
664 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
665 memset(&vb[1], kLine_Verb, count - 1);
666 } else {
667 for (int i = 1; i < count; ++i) {
668 vb[i] = kLine_Verb;
669 }
670 }
671 fSegmentMask |= kLine_SegmentMask;
672 }
673 if (close) {
674 vb[count] = kClose_Verb;
675 }
676
677 GEN_ID_INC;
678 DIRTY_AFTER_EDIT;
679}
680
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
682
683void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
684 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 SkScalar w = rect.width();
686 SkScalar halfW = SkScalarHalf(w);
687 SkScalar h = rect.height();
688 SkScalar halfH = SkScalarHalf(h);
689
690 if (halfW <= 0 || halfH <= 0) {
691 return;
692 }
693
reed@google.comabf15c12011-01-18 20:35:51 +0000694 bool skip_hori = rx >= halfW;
695 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696
697 if (skip_hori && skip_vert) {
698 this->addOval(rect, dir);
699 return;
700 }
reed@google.comabf15c12011-01-18 20:35:51 +0000701
702 SkAutoPathBoundsUpdate apbu(this, rect);
703
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 if (skip_hori) {
705 rx = halfW;
706 } else if (skip_vert) {
707 ry = halfH;
708 }
709
710 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
711 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
712
713 this->incReserve(17);
714 this->moveTo(rect.fRight - rx, rect.fTop);
715 if (dir == kCCW_Direction) {
716 if (!skip_hori) {
717 this->lineTo(rect.fLeft + rx, rect.fTop); // top
718 }
719 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
720 rect.fLeft, rect.fTop + ry - sy,
721 rect.fLeft, rect.fTop + ry); // top-left
722 if (!skip_vert) {
723 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
724 }
725 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
726 rect.fLeft + rx - sx, rect.fBottom,
727 rect.fLeft + rx, rect.fBottom); // bot-left
728 if (!skip_hori) {
729 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
730 }
731 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
732 rect.fRight, rect.fBottom - ry + sy,
733 rect.fRight, rect.fBottom - ry); // bot-right
734 if (!skip_vert) {
735 this->lineTo(rect.fRight, rect.fTop + ry);
736 }
737 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
738 rect.fRight - rx + sx, rect.fTop,
739 rect.fRight - rx, rect.fTop); // top-right
740 } else {
741 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
742 rect.fRight, rect.fTop + ry - sy,
743 rect.fRight, rect.fTop + ry); // top-right
744 if (!skip_vert) {
745 this->lineTo(rect.fRight, rect.fBottom - ry);
746 }
747 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
748 rect.fRight - rx + sx, rect.fBottom,
749 rect.fRight - rx, rect.fBottom); // bot-right
750 if (!skip_hori) {
751 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
752 }
753 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
754 rect.fLeft, rect.fBottom - ry + sy,
755 rect.fLeft, rect.fBottom - ry); // bot-left
756 if (!skip_vert) {
757 this->lineTo(rect.fLeft, rect.fTop + ry); // left
758 }
759 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
760 rect.fLeft + rx - sx, rect.fTop,
761 rect.fLeft + rx, rect.fTop); // top-left
762 if (!skip_hori) {
763 this->lineTo(rect.fRight - rx, rect.fTop); // top
764 }
765 }
766 this->close();
767}
768
769static void add_corner_arc(SkPath* path, const SkRect& rect,
770 SkScalar rx, SkScalar ry, int startAngle,
771 SkPath::Direction dir, bool forceMoveTo) {
772 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
773 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000774
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775 SkRect r;
776 r.set(-rx, -ry, rx, ry);
777
778 switch (startAngle) {
779 case 0:
780 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
781 break;
782 case 90:
783 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
784 break;
785 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
786 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000787 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 }
reed@google.comabf15c12011-01-18 20:35:51 +0000789
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 SkScalar start = SkIntToScalar(startAngle);
791 SkScalar sweep = SkIntToScalar(90);
792 if (SkPath::kCCW_Direction == dir) {
793 start += sweep;
794 sweep = -sweep;
795 }
reed@google.comabf15c12011-01-18 20:35:51 +0000796
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 path->arcTo(r, start, sweep, forceMoveTo);
798}
799
800void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
801 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +0000802 // abort before we invoke SkAutoPathBoundsUpdate()
803 if (rect.isEmpty()) {
804 return;
805 }
806
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 SkAutoPathBoundsUpdate apbu(this, rect);
808
809 if (kCW_Direction == dir) {
810 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
811 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
812 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
813 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
814 } else {
815 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
816 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
817 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
818 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
819 }
820 this->close();
821}
822
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000823bool SkPath::hasOnlyMoveTos() const {
824 const uint8_t* verbs = fVerbs.begin();
825 const uint8_t* verbStop = fVerbs.end();
826 while (verbs != verbStop) {
827 if (*verbs == kLine_Verb ||
828 *verbs == kQuad_Verb ||
829 *verbs == kCubic_Verb) {
830 return false;
831 }
832 ++verbs;
833 }
834 return true;
835}
836
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000838 /* If addOval() is called after previous moveTo(),
839 this path is still marked as an oval. This is used to
840 fit into WebKit's calling sequences.
841 We can't simply check isEmpty() in this case, as additional
842 moveTo() would mark the path non empty.
843 */
844 fIsOval = hasOnlyMoveTos();
845
846 SkAutoDisableOvalCheck adoc(this);
847
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 SkAutoPathBoundsUpdate apbu(this, oval);
849
850 SkScalar cx = oval.centerX();
851 SkScalar cy = oval.centerY();
852 SkScalar rx = SkScalarHalf(oval.width());
853 SkScalar ry = SkScalarHalf(oval.height());
854#if 0 // these seem faster than using quads (1/2 the number of edges)
855 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
856 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
857
858 this->incReserve(13);
859 this->moveTo(cx + rx, cy);
860 if (dir == kCCW_Direction) {
861 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
862 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
863 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
864 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
865 } else {
866 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
867 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
868 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
869 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
870 }
871#else
872 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
873 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
874 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
875 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
876
877 /*
878 To handle imprecision in computing the center and radii, we revert to
879 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
880 to ensure that we don't exceed the oval's bounds *ever*, since we want
881 to use oval for our fast-bounds, rather than have to recompute it.
882 */
883 const SkScalar L = oval.fLeft; // cx - rx
884 const SkScalar T = oval.fTop; // cy - ry
885 const SkScalar R = oval.fRight; // cx + rx
886 const SkScalar B = oval.fBottom; // cy + ry
887
888 this->incReserve(17); // 8 quads + close
889 this->moveTo(R, cy);
890 if (dir == kCCW_Direction) {
891 this->quadTo( R, cy - sy, cx + mx, cy - my);
892 this->quadTo(cx + sx, T, cx , T);
893 this->quadTo(cx - sx, T, cx - mx, cy - my);
894 this->quadTo( L, cy - sy, L, cy );
895 this->quadTo( L, cy + sy, cx - mx, cy + my);
896 this->quadTo(cx - sx, B, cx , B);
897 this->quadTo(cx + sx, B, cx + mx, cy + my);
898 this->quadTo( R, cy + sy, R, cy );
899 } else {
900 this->quadTo( R, cy + sy, cx + mx, cy + my);
901 this->quadTo(cx + sx, B, cx , B);
902 this->quadTo(cx - sx, B, cx - mx, cy + my);
903 this->quadTo( L, cy + sy, L, cy );
904 this->quadTo( L, cy - sy, cx - mx, cy - my);
905 this->quadTo(cx - sx, T, cx , T);
906 this->quadTo(cx + sx, T, cx + mx, cy - my);
907 this->quadTo( R, cy - sy, R, cy );
908 }
909#endif
910 this->close();
911}
912
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000913bool SkPath::isOval(SkRect* rect) const {
914 if (fIsOval && rect) {
915 *rect = getBounds();
916 }
917
918 return fIsOval;
919}
920
reed@android.com8a1c16f2008-12-17 15:59:43 +0000921void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
922 if (r > 0) {
923 SkRect rect;
924 rect.set(x - r, y - r, x + r, y + r);
925 this->addOval(rect, dir);
926 }
927}
928
929#include "SkGeometry.h"
930
931static int build_arc_points(const SkRect& oval, SkScalar startAngle,
932 SkScalar sweepAngle,
933 SkPoint pts[kSkBuildQuadArcStorage]) {
934 SkVector start, stop;
935
936 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
937 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
938 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +0000939
940 /* If the sweep angle is nearly (but less than) 360, then due to precision
941 loss in radians-conversion and/or sin/cos, we may end up with coincident
942 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
943 of drawing a nearly complete circle (good).
944 e.g. canvas.drawArc(0, 359.99, ...)
945 -vs- canvas.drawArc(0, 359.9, ...)
946 We try to detect this edge case, and tweak the stop vector
947 */
948 if (start == stop) {
949 SkScalar sw = SkScalarAbs(sweepAngle);
950 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
951 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
952 // make a guess at a tiny angle (in radians) to tweak by
953 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
954 // not sure how much will be enough, so we use a loop
955 do {
956 stopRad -= deltaRad;
957 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
958 } while (start == stop);
959 }
960 }
961
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +0000963
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
965 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +0000966
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 return SkBuildQuadArc(start, stop,
968 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
969 &matrix, pts);
970}
971
972void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
973 bool forceMoveTo) {
974 if (oval.width() < 0 || oval.height() < 0) {
975 return;
976 }
977
978 SkPoint pts[kSkBuildQuadArcStorage];
979 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
980 SkASSERT((count & 1) == 1);
981
982 if (fVerbs.count() == 0) {
983 forceMoveTo = true;
984 }
985 this->incReserve(count);
986 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
987 for (int i = 1; i < count; i += 2) {
988 this->quadTo(pts[i], pts[i+1]);
989 }
990}
991
992void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
993 SkScalar sweepAngle) {
994 if (oval.isEmpty() || 0 == sweepAngle) {
995 return;
996 }
997
998 const SkScalar kFullCircleAngle = SkIntToScalar(360);
999
1000 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1001 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1002 return;
1003 }
1004
1005 SkPoint pts[kSkBuildQuadArcStorage];
1006 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1007
1008 this->incReserve(count);
1009 this->moveTo(pts[0]);
1010 for (int i = 1; i < count; i += 2) {
1011 this->quadTo(pts[i], pts[i+1]);
1012 }
1013}
1014
1015/*
1016 Need to handle the case when the angle is sharp, and our computed end-points
1017 for the arc go behind pt1 and/or p2...
1018*/
1019void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1020 SkScalar radius) {
1021 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001022
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023 // need to know our prev pt so we can construct tangent vectors
1024 {
1025 SkPoint start;
1026 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001027 // Handle degenerate cases by adding a line to the first point and
1028 // bailing out.
1029 if ((x1 == start.fX && y1 == start.fY) ||
1030 (x1 == x2 && y1 == y2) ||
1031 radius == 0) {
1032 this->lineTo(x1, y1);
1033 return;
1034 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001035 before.setNormalize(x1 - start.fX, y1 - start.fY);
1036 after.setNormalize(x2 - x1, y2 - y1);
1037 }
reed@google.comabf15c12011-01-18 20:35:51 +00001038
reed@android.com8a1c16f2008-12-17 15:59:43 +00001039 SkScalar cosh = SkPoint::DotProduct(before, after);
1040 SkScalar sinh = SkPoint::CrossProduct(before, after);
1041
1042 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001043 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001044 return;
1045 }
reed@google.comabf15c12011-01-18 20:35:51 +00001046
reed@android.com8a1c16f2008-12-17 15:59:43 +00001047 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1048 if (dist < 0) {
1049 dist = -dist;
1050 }
1051
1052 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1053 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1054 SkRotationDirection arcDir;
1055
1056 // now turn before/after into normals
1057 if (sinh > 0) {
1058 before.rotateCCW();
1059 after.rotateCCW();
1060 arcDir = kCW_SkRotationDirection;
1061 } else {
1062 before.rotateCW();
1063 after.rotateCW();
1064 arcDir = kCCW_SkRotationDirection;
1065 }
1066
1067 SkMatrix matrix;
1068 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001069
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070 matrix.setScale(radius, radius);
1071 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1072 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001073
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001075
reed@android.com8a1c16f2008-12-17 15:59:43 +00001076 this->incReserve(count);
1077 // [xx,yy] == pts[0]
1078 this->lineTo(xx, yy);
1079 for (int i = 1; i < count; i += 2) {
1080 this->quadTo(pts[i], pts[i+1]);
1081 }
1082}
1083
1084///////////////////////////////////////////////////////////////////////////////
1085
1086void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1087 SkMatrix matrix;
1088
1089 matrix.setTranslate(dx, dy);
1090 this->addPath(path, matrix);
1091}
1092
1093void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
1094 this->incReserve(path.fPts.count());
1095
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001096 fIsOval = false;
1097
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001098 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001099 SkPoint pts[4];
1100 Verb verb;
1101
1102 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1103
1104 while ((verb = iter.next(pts)) != kDone_Verb) {
1105 switch (verb) {
1106 case kMove_Verb:
1107 proc(matrix, &pts[0], &pts[0], 1);
1108 this->moveTo(pts[0]);
1109 break;
1110 case kLine_Verb:
1111 proc(matrix, &pts[1], &pts[1], 1);
1112 this->lineTo(pts[1]);
1113 break;
1114 case kQuad_Verb:
1115 proc(matrix, &pts[1], &pts[1], 2);
1116 this->quadTo(pts[1], pts[2]);
1117 break;
1118 case kCubic_Verb:
1119 proc(matrix, &pts[1], &pts[1], 3);
1120 this->cubicTo(pts[1], pts[2], pts[3]);
1121 break;
1122 case kClose_Verb:
1123 this->close();
1124 break;
1125 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001126 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127 }
1128 }
1129}
1130
1131///////////////////////////////////////////////////////////////////////////////
1132
1133static const uint8_t gPtsInVerb[] = {
1134 1, // kMove
1135 1, // kLine
1136 2, // kQuad
1137 3, // kCubic
1138 0, // kClose
1139 0 // kDone
1140};
1141
1142// ignore the initial moveto, and stop when the 1st contour ends
1143void SkPath::pathTo(const SkPath& path) {
1144 int i, vcount = path.fVerbs.count();
1145 if (vcount == 0) {
1146 return;
1147 }
1148
1149 this->incReserve(vcount);
1150
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001151 fIsOval = false;
1152
reed@android.com8a1c16f2008-12-17 15:59:43 +00001153 const uint8_t* verbs = path.fVerbs.begin();
1154 const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
1155
1156 SkASSERT(verbs[0] == kMove_Verb);
1157 for (i = 1; i < vcount; i++) {
1158 switch (verbs[i]) {
1159 case kLine_Verb:
1160 this->lineTo(pts[0].fX, pts[0].fY);
1161 break;
1162 case kQuad_Verb:
1163 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1164 break;
1165 case kCubic_Verb:
1166 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY,
1167 pts[2].fX, pts[2].fY);
1168 break;
1169 case kClose_Verb:
1170 return;
1171 }
1172 pts += gPtsInVerb[verbs[i]];
1173 }
1174}
1175
1176// ignore the last point of the 1st contour
1177void SkPath::reversePathTo(const SkPath& path) {
1178 int i, vcount = path.fVerbs.count();
1179 if (vcount == 0) {
1180 return;
1181 }
1182
1183 this->incReserve(vcount);
1184
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001185 fIsOval = false;
1186
reed@android.com8a1c16f2008-12-17 15:59:43 +00001187 const uint8_t* verbs = path.fVerbs.begin();
1188 const SkPoint* pts = path.fPts.begin();
1189
1190 SkASSERT(verbs[0] == kMove_Verb);
1191 for (i = 1; i < vcount; i++) {
1192 int n = gPtsInVerb[verbs[i]];
1193 if (n == 0) {
1194 break;
1195 }
1196 pts += n;
1197 }
1198
1199 while (--i > 0) {
1200 switch (verbs[i]) {
1201 case kLine_Verb:
1202 this->lineTo(pts[-1].fX, pts[-1].fY);
1203 break;
1204 case kQuad_Verb:
1205 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1206 break;
1207 case kCubic_Verb:
1208 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1209 pts[-3].fX, pts[-3].fY);
1210 break;
1211 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001212 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001213 break;
1214 }
1215 pts -= gPtsInVerb[verbs[i]];
1216 }
1217}
1218
reed@google.com63d73742012-01-10 15:33:12 +00001219void SkPath::reverseAddPath(const SkPath& src) {
1220 this->incReserve(src.fPts.count());
1221
reed@google.com63d73742012-01-10 15:33:12 +00001222 const SkPoint* pts = src.fPts.end();
1223 const uint8_t* startVerbs = src.fVerbs.begin();
1224 const uint8_t* verbs = src.fVerbs.end();
1225
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001226 fIsOval = false;
1227
reed@google.com63d73742012-01-10 15:33:12 +00001228 bool needMove = true;
1229 bool needClose = false;
1230 while (verbs > startVerbs) {
1231 uint8_t v = *--verbs;
1232 int n = gPtsInVerb[v];
1233
1234 if (needMove) {
1235 --pts;
1236 this->moveTo(pts->fX, pts->fY);
1237 needMove = false;
1238 }
1239 pts -= n;
1240 switch (v) {
1241 case kMove_Verb:
1242 if (needClose) {
1243 this->close();
1244 needClose = false;
1245 }
1246 needMove = true;
1247 pts += 1; // so we see the point in "if (needMove)" above
1248 break;
1249 case kLine_Verb:
1250 this->lineTo(pts[0]);
1251 break;
1252 case kQuad_Verb:
1253 this->quadTo(pts[1], pts[0]);
1254 break;
1255 case kCubic_Verb:
1256 this->cubicTo(pts[2], pts[1], pts[0]);
1257 break;
1258 case kClose_Verb:
1259 needClose = true;
1260 break;
1261 default:
1262 SkASSERT(!"unexpected verb");
1263 }
1264 }
1265}
1266
reed@android.com8a1c16f2008-12-17 15:59:43 +00001267///////////////////////////////////////////////////////////////////////////////
1268
1269void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1270 SkMatrix matrix;
1271
1272 matrix.setTranslate(dx, dy);
1273 this->transform(matrix, dst);
1274}
1275
1276#include "SkGeometry.h"
1277
1278static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1279 int level = 2) {
1280 if (--level >= 0) {
1281 SkPoint tmp[5];
1282
1283 SkChopQuadAtHalf(pts, tmp);
1284 subdivide_quad_to(path, &tmp[0], level);
1285 subdivide_quad_to(path, &tmp[2], level);
1286 } else {
1287 path->quadTo(pts[1], pts[2]);
1288 }
1289}
1290
1291static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1292 int level = 2) {
1293 if (--level >= 0) {
1294 SkPoint tmp[7];
1295
1296 SkChopCubicAtHalf(pts, tmp);
1297 subdivide_cubic_to(path, &tmp[0], level);
1298 subdivide_cubic_to(path, &tmp[3], level);
1299 } else {
1300 path->cubicTo(pts[1], pts[2], pts[3]);
1301 }
1302}
1303
1304void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1305 SkDEBUGCODE(this->validate();)
1306 if (dst == NULL) {
1307 dst = (SkPath*)this;
1308 }
1309
tomhudson@google.com8d430182011-06-06 19:11:19 +00001310 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001311 SkPath tmp;
1312 tmp.fFillType = fFillType;
1313
1314 SkPath::Iter iter(*this, false);
1315 SkPoint pts[4];
1316 SkPath::Verb verb;
1317
reed@google.com4a3b7142012-05-16 17:16:46 +00001318 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 switch (verb) {
1320 case kMove_Verb:
1321 tmp.moveTo(pts[0]);
1322 break;
1323 case kLine_Verb:
1324 tmp.lineTo(pts[1]);
1325 break;
1326 case kQuad_Verb:
1327 subdivide_quad_to(&tmp, pts);
1328 break;
1329 case kCubic_Verb:
1330 subdivide_cubic_to(&tmp, pts);
1331 break;
1332 case kClose_Verb:
1333 tmp.close();
1334 break;
1335 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001336 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 break;
1338 }
1339 }
1340
1341 dst->swap(tmp);
1342 matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
1343 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001344 /*
1345 * If we're not in perspective, we can transform all of the points at
1346 * once.
1347 *
1348 * Here we also want to optimize bounds, by noting if the bounds are
1349 * already known, and if so, we just transform those as well and mark
1350 * them as "known", rather than force the transformed path to have to
1351 * recompute them.
1352 *
1353 * Special gotchas if the path is effectively empty (<= 1 point) or
1354 * if it is non-finite. In those cases bounds need to stay empty,
1355 * regardless of the matrix.
1356 */
reed@android.comd252db02009-04-01 18:31:44 +00001357 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001358 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001359 if (fIsFinite) {
1360 matrix.mapRect(&dst->fBounds, fBounds);
1361 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1362 dst->fBounds.setEmpty();
1363 }
1364 } else {
1365 dst->fIsFinite = false;
1366 dst->fBounds.setEmpty();
1367 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001368 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001369 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001370 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371 }
1372
1373 if (this != dst) {
1374 dst->fVerbs = fVerbs;
1375 dst->fPts.setCount(fPts.count());
1376 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001377 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001378 dst->fConvexity = fConvexity;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001379 dst->fIsOval = fIsOval;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001381
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001383
1384 if (fIsOval) {
1385 // It's an oval only if it stays a rect.
1386 dst->fIsOval = matrix.rectStaysRect();
1387 }
1388
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389 SkDEBUGCODE(dst->validate();)
1390 }
1391}
1392
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393///////////////////////////////////////////////////////////////////////////////
1394///////////////////////////////////////////////////////////////////////////////
1395
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001396enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001397 kEmptyContour_SegmentState, // The current contour is empty. We may be
1398 // starting processing or we may have just
1399 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001400 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1401 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1402 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403};
1404
1405SkPath::Iter::Iter() {
1406#ifdef SK_DEBUG
1407 fPts = NULL;
1408 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001409 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001410 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411#endif
1412 // need to init enough to make next() harmlessly return kDone_Verb
1413 fVerbs = NULL;
1414 fVerbStop = NULL;
1415 fNeedClose = false;
1416}
1417
1418SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1419 this->setPath(path, forceClose);
1420}
1421
1422void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
1423 fPts = path.fPts.begin();
1424 fVerbs = path.fVerbs.begin();
1425 fVerbStop = path.fVerbs.end();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001426 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001427 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428 fForceClose = SkToU8(forceClose);
1429 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001430 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431}
1432
1433bool SkPath::Iter::isClosedContour() const {
1434 if (fVerbs == NULL || fVerbs == fVerbStop) {
1435 return false;
1436 }
1437 if (fForceClose) {
1438 return true;
1439 }
1440
1441 const uint8_t* verbs = fVerbs;
1442 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001443
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444 if (kMove_Verb == *verbs) {
1445 verbs += 1; // skip the initial moveto
1446 }
1447
1448 while (verbs < stop) {
reed@google.comabf15c12011-01-18 20:35:51 +00001449 unsigned v = *verbs++;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450 if (kMove_Verb == v) {
1451 break;
1452 }
1453 if (kClose_Verb == v) {
1454 return true;
1455 }
1456 }
1457 return false;
1458}
1459
1460SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001461 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001463 // A special case: if both points are NaN, SkPoint::operation== returns
1464 // false, but the iterator expects that they are treated as the same.
1465 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001466 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1467 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001468 return kClose_Verb;
1469 }
1470
reed@google.com9e25dbf2012-05-15 17:05:38 +00001471 pts[0] = fLastPt;
1472 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 fLastPt = fMoveTo;
1474 fCloseLine = true;
1475 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001476 } else {
1477 pts[0] = fMoveTo;
1478 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480}
1481
reed@google.com9e25dbf2012-05-15 17:05:38 +00001482const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001483 if (fSegmentState == kAfterMove_SegmentState) {
1484 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001485 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001486 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001488 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1489 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001490 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492}
1493
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001494void SkPath::Iter::consumeDegenerateSegments() {
1495 // We need to step over anything that will not move the current draw point
1496 // forward before the next move is seen
1497 const uint8_t* lastMoveVerb = 0;
1498 const SkPoint* lastMovePt = 0;
1499 SkPoint lastPt = fLastPt;
1500 while (fVerbs != fVerbStop) {
1501 unsigned verb = *fVerbs;
1502 switch (verb) {
1503 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001504 // Keep a record of this most recent move
1505 lastMoveVerb = fVerbs;
1506 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001507 lastPt = fPts[0];
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001508 fVerbs++;
1509 fPts++;
1510 break;
1511
1512 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001513 // A close when we are in a segment is always valid except when it
1514 // follows a move which follows a segment.
1515 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001516 return;
1517 }
1518 // A close at any other time must be ignored
1519 fVerbs++;
1520 break;
1521
1522 case kLine_Verb:
1523 if (!IsLineDegenerate(lastPt, fPts[0])) {
1524 if (lastMoveVerb) {
1525 fVerbs = lastMoveVerb;
1526 fPts = lastMovePt;
1527 return;
1528 }
1529 return;
1530 }
1531 // Ignore this line and continue
1532 fVerbs++;
1533 fPts++;
1534 break;
1535
1536 case kQuad_Verb:
1537 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1538 if (lastMoveVerb) {
1539 fVerbs = lastMoveVerb;
1540 fPts = lastMovePt;
1541 return;
1542 }
1543 return;
1544 }
1545 // Ignore this line and continue
1546 fVerbs++;
1547 fPts += 2;
1548 break;
1549
1550 case kCubic_Verb:
1551 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1552 if (lastMoveVerb) {
1553 fVerbs = lastMoveVerb;
1554 fPts = lastMovePt;
1555 return;
1556 }
1557 return;
1558 }
1559 // Ignore this line and continue
1560 fVerbs++;
1561 fPts += 3;
1562 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001563
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001564 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001565 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001566 }
1567 }
1568}
1569
reed@google.com4a3b7142012-05-16 17:16:46 +00001570SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001571 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001572
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001574 // Close the curve if requested and if there is some curve to close
1575 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001576 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 return kLine_Verb;
1578 }
1579 fNeedClose = false;
1580 return kClose_Verb;
1581 }
1582 return kDone_Verb;
1583 }
1584
1585 unsigned verb = *fVerbs++;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001586 const SkPoint* SK_RESTRICT srcPts = fPts;
1587 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588
1589 switch (verb) {
1590 case kMove_Verb:
1591 if (fNeedClose) {
1592 fVerbs -= 1;
1593 verb = this->autoClose(pts);
1594 if (verb == kClose_Verb) {
1595 fNeedClose = false;
1596 }
1597 return (Verb)verb;
1598 }
1599 if (fVerbs == fVerbStop) { // might be a trailing moveto
1600 return kDone_Verb;
1601 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001602 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001603 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001605 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001606 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 fNeedClose = fForceClose;
1608 break;
1609 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001610 pts[0] = this->cons_moveTo();
1611 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 fLastPt = srcPts[0];
1613 fCloseLine = false;
1614 srcPts += 1;
1615 break;
1616 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001617 pts[0] = this->cons_moveTo();
1618 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 fLastPt = srcPts[1];
1620 srcPts += 2;
1621 break;
1622 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001623 pts[0] = this->cons_moveTo();
1624 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625 fLastPt = srcPts[2];
1626 srcPts += 3;
1627 break;
1628 case kClose_Verb:
1629 verb = this->autoClose(pts);
1630 if (verb == kLine_Verb) {
1631 fVerbs -= 1;
1632 } else {
1633 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001634 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001636 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 break;
1638 }
1639 fPts = srcPts;
1640 return (Verb)verb;
1641}
1642
1643///////////////////////////////////////////////////////////////////////////////
1644
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001645SkPath::RawIter::RawIter() {
1646#ifdef SK_DEBUG
1647 fPts = NULL;
1648 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1649#endif
1650 // need to init enough to make next() harmlessly return kDone_Verb
1651 fVerbs = NULL;
1652 fVerbStop = NULL;
1653}
1654
1655SkPath::RawIter::RawIter(const SkPath& path) {
1656 this->setPath(path);
1657}
1658
1659void SkPath::RawIter::setPath(const SkPath& path) {
1660 fPts = path.fPts.begin();
1661 fVerbs = path.fVerbs.begin();
1662 fVerbStop = path.fVerbs.end();
1663 fMoveTo.fX = fMoveTo.fY = 0;
1664 fLastPt.fX = fLastPt.fY = 0;
1665}
1666
1667SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001668 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001669 if (fVerbs == fVerbStop) {
1670 return kDone_Verb;
1671 }
1672
1673 unsigned verb = *fVerbs++;
1674 const SkPoint* srcPts = fPts;
1675
1676 switch (verb) {
1677 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001678 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001679 fMoveTo = srcPts[0];
1680 fLastPt = fMoveTo;
1681 srcPts += 1;
1682 break;
1683 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001684 pts[0] = fLastPt;
1685 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001686 fLastPt = srcPts[0];
1687 srcPts += 1;
1688 break;
1689 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001690 pts[0] = fLastPt;
1691 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001692 fLastPt = srcPts[1];
1693 srcPts += 2;
1694 break;
1695 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001696 pts[0] = fLastPt;
1697 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001698 fLastPt = srcPts[2];
1699 srcPts += 3;
1700 break;
1701 case kClose_Verb:
1702 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001703 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001704 break;
1705 }
1706 fPts = srcPts;
1707 return (Verb)verb;
1708}
1709
1710///////////////////////////////////////////////////////////////////////////////
1711
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001713 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714*/
1715
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001716uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 SkDEBUGCODE(this->validate();)
1718
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001719 if (NULL == storage) {
1720 const int byteCount = 3 * sizeof(int32_t)
1721 + sizeof(SkPoint) * fPts.count()
1722 + sizeof(uint8_t) * fVerbs.count();
1723 return SkAlign4(byteCount);
1724 }
1725
1726 SkWBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 buffer.write32(fPts.count());
1728 buffer.write32(fVerbs.count());
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001729 int32_t packed = (fIsOval << 24) | (fConvexity << 16) |
1730 (fFillType << 8) | fSegmentMask;
1731 buffer.write32(packed);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001732 buffer.write(fPts.begin(), sizeof(SkPoint) * fPts.count());
1733 buffer.write(fVerbs.begin(), fVerbs.count());
1734 buffer.padToAlign4();
1735 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736}
1737
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001738uint32_t SkPath::readFromMemory(const void* storage) {
1739 SkRBuffer buffer(storage);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 fPts.setCount(buffer.readS32());
1741 fVerbs.setCount(buffer.readS32());
reed@google.com98b11f12011-09-21 18:40:27 +00001742 uint32_t packed = buffer.readS32();
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001743 fFillType = (packed >> 8) & 0xFF;
reed@google.com98b11f12011-09-21 18:40:27 +00001744 fSegmentMask = packed & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
1746 buffer.read(fVerbs.begin(), fVerbs.count());
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001747 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001748
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001749 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +00001750 DIRTY_AFTER_EDIT;
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001751 // DIRTY_AFTER_EDIT resets fIsOval and fConvexity
1752 fIsOval = packed >> 24;
1753 fConvexity = (packed >> 16) & 0xFF;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754
1755 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001756 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001757}
1758
1759///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761void SkPath::dump(bool forceClose, const char title[]) const {
1762 Iter iter(*this, forceClose);
1763 SkPoint pts[4];
1764 Verb verb;
1765
1766 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
1767 title ? title : "");
1768
reed@google.com4a3b7142012-05-16 17:16:46 +00001769 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 switch (verb) {
1771 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 SkDebugf(" path: moveTo [%g %g]\n",
1773 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 break;
1775 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 SkDebugf(" path: lineTo [%g %g]\n",
1777 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 break;
1779 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
1781 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1782 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 break;
1784 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
1786 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
1787 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
1788 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 break;
1790 case kClose_Verb:
1791 SkDebugf(" path: close\n");
1792 break;
1793 default:
1794 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
1795 verb = kDone_Verb; // stop the loop
1796 break;
1797 }
1798 }
1799 SkDebugf("path: done %s\n", title ? title : "");
1800}
1801
reed@android.come522ca52009-11-23 20:10:41 +00001802void SkPath::dump() const {
1803 this->dump(false);
1804}
1805
1806#ifdef SK_DEBUG
1807void SkPath::validate() const {
1808 SkASSERT(this != NULL);
1809 SkASSERT((fFillType & ~3) == 0);
1810 fPts.validate();
1811 fVerbs.validate();
reed@google.comabf15c12011-01-18 20:35:51 +00001812
reed@android.come522ca52009-11-23 20:10:41 +00001813 if (!fBoundsIsDirty) {
1814 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001815
1816 bool isFinite = compute_pt_bounds(&bounds, fPts);
1817 SkASSERT(fIsFinite == isFinite);
1818
reed@android.come522ca52009-11-23 20:10:41 +00001819 if (fPts.count() <= 1) {
1820 // if we're empty, fBounds may be empty but translated, so we can't
1821 // necessarily compare to bounds directly
1822 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
1823 // be [2, 2, 2, 2]
1824 SkASSERT(bounds.isEmpty());
1825 SkASSERT(fBounds.isEmpty());
1826 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00001827 if (bounds.isEmpty()) {
1828 SkASSERT(fBounds.isEmpty());
1829 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00001830 if (!fBounds.isEmpty()) {
1831 SkASSERT(fBounds.contains(bounds));
1832 }
reed@google.comeac52bd2011-11-14 18:13:59 +00001833 }
reed@android.come522ca52009-11-23 20:10:41 +00001834 }
1835 }
reed@google.com10296cc2011-09-21 12:29:05 +00001836
1837 uint32_t mask = 0;
1838 for (int i = 0; i < fVerbs.count(); i++) {
1839 switch (fVerbs[i]) {
1840 case kLine_Verb:
1841 mask |= kLine_SegmentMask;
1842 break;
1843 case kQuad_Verb:
1844 mask |= kQuad_SegmentMask;
1845 break;
1846 case kCubic_Verb:
1847 mask |= kCubic_SegmentMask;
1848 }
1849 }
1850 SkASSERT(mask == fSegmentMask);
reed@android.come522ca52009-11-23 20:10:41 +00001851}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852#endif
reed@android.come522ca52009-11-23 20:10:41 +00001853
reed@google.com04863fa2011-05-15 04:08:24 +00001854///////////////////////////////////////////////////////////////////////////////
1855
reed@google.com0b7b9822011-05-16 12:29:27 +00001856static int sign(SkScalar x) { return x < 0; }
1857#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00001858
reed@google.com04863fa2011-05-15 04:08:24 +00001859static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00001860 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00001861}
1862
1863// only valid for a single contour
1864struct Convexicator {
reed@google.comb54455e2011-05-16 14:16:04 +00001865 Convexicator() : fPtCount(0), fConvexity(SkPath::kConvex_Convexity) {
reed@google.com04863fa2011-05-15 04:08:24 +00001866 fSign = 0;
1867 // warnings
1868 fCurrPt.set(0, 0);
1869 fVec0.set(0, 0);
1870 fVec1.set(0, 0);
1871 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00001872
1873 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00001874 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00001875 }
1876
1877 SkPath::Convexity getConvexity() const { return fConvexity; }
1878
1879 void addPt(const SkPoint& pt) {
1880 if (SkPath::kConcave_Convexity == fConvexity) {
1881 return;
1882 }
1883
1884 if (0 == fPtCount) {
1885 fCurrPt = pt;
1886 ++fPtCount;
1887 } else {
1888 SkVector vec = pt - fCurrPt;
1889 if (vec.fX || vec.fY) {
1890 fCurrPt = pt;
1891 if (++fPtCount == 2) {
1892 fFirstVec = fVec1 = vec;
1893 } else {
1894 SkASSERT(fPtCount > 2);
1895 this->addVec(vec);
1896 }
reed@google.com85b6e392011-05-15 20:25:17 +00001897
1898 int sx = sign(vec.fX);
1899 int sy = sign(vec.fY);
1900 fDx += (sx != fSx);
1901 fDy += (sy != fSy);
1902 fSx = sx;
1903 fSy = sy;
1904
1905 if (fDx > 3 || fDy > 3) {
1906 fConvexity = SkPath::kConcave_Convexity;
1907 }
reed@google.com04863fa2011-05-15 04:08:24 +00001908 }
1909 }
1910 }
1911
1912 void close() {
1913 if (fPtCount > 2) {
1914 this->addVec(fFirstVec);
1915 }
1916 }
1917
1918private:
1919 void addVec(const SkVector& vec) {
1920 SkASSERT(vec.fX || vec.fY);
1921 fVec0 = fVec1;
1922 fVec1 = vec;
1923 int sign = CrossProductSign(fVec0, fVec1);
1924 if (0 == fSign) {
1925 fSign = sign;
1926 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00001927 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00001928 fConvexity = SkPath::kConcave_Convexity;
1929 }
1930 }
1931 }
1932
1933 SkPoint fCurrPt;
1934 SkVector fVec0, fVec1, fFirstVec;
1935 int fPtCount; // non-degenerate points
1936 int fSign;
1937 SkPath::Convexity fConvexity;
reed@google.com0b7b9822011-05-16 12:29:27 +00001938 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00001939};
1940
1941SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
1942 SkPoint pts[4];
1943 SkPath::Verb verb;
1944 SkPath::Iter iter(path, true);
1945
1946 int contourCount = 0;
1947 int count;
1948 Convexicator state;
1949
1950 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
1951 switch (verb) {
1952 case kMove_Verb:
1953 if (++contourCount > 1) {
1954 return kConcave_Convexity;
1955 }
1956 pts[1] = pts[0];
1957 count = 1;
1958 break;
1959 case kLine_Verb: count = 1; break;
1960 case kQuad_Verb: count = 2; break;
1961 case kCubic_Verb: count = 3; break;
1962 case kClose_Verb:
1963 state.close();
1964 count = 0;
1965 break;
1966 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001967 SkDEBUGFAIL("bad verb");
reed@google.com04863fa2011-05-15 04:08:24 +00001968 return kConcave_Convexity;
1969 }
1970
1971 for (int i = 1; i <= count; i++) {
1972 state.addPt(pts[i]);
1973 }
1974 // early exit
1975 if (kConcave_Convexity == state.getConvexity()) {
1976 return kConcave_Convexity;
1977 }
1978 }
1979 return state.getConvexity();
1980}
reed@google.com69a99432012-01-10 18:00:10 +00001981
1982///////////////////////////////////////////////////////////////////////////////
1983
1984class ContourIter {
1985public:
1986 ContourIter(const SkTDArray<uint8_t>& verbs, const SkTDArray<SkPoint>& pts);
1987
1988 bool done() const { return fDone; }
1989 // if !done() then these may be called
1990 int count() const { return fCurrPtCount; }
1991 const SkPoint* pts() const { return fCurrPt; }
1992 void next();
1993
1994private:
1995 int fCurrPtCount;
1996 const SkPoint* fCurrPt;
1997 const uint8_t* fCurrVerb;
1998 const uint8_t* fStopVerbs;
1999 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002000 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002001};
2002
2003ContourIter::ContourIter(const SkTDArray<uint8_t>& verbs,
2004 const SkTDArray<SkPoint>& pts) {
2005 fStopVerbs = verbs.begin() + verbs.count();
2006
2007 fDone = false;
2008 fCurrPt = pts.begin();
2009 fCurrVerb = verbs.begin();
2010 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002011 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002012 this->next();
2013}
2014
2015void ContourIter::next() {
2016 if (fCurrVerb >= fStopVerbs) {
2017 fDone = true;
2018 }
2019 if (fDone) {
2020 return;
2021 }
2022
2023 // skip pts of prev contour
2024 fCurrPt += fCurrPtCount;
2025
2026 SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]);
2027 int ptCount = 1; // moveTo
2028 const uint8_t* verbs = fCurrVerb;
2029
2030 for (++verbs; verbs < fStopVerbs; ++verbs) {
2031 switch (*verbs) {
2032 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002033 goto CONTOUR_END;
2034 case SkPath::kLine_Verb:
2035 ptCount += 1;
2036 break;
2037 case SkPath::kQuad_Verb:
2038 ptCount += 2;
2039 break;
2040 case SkPath::kCubic_Verb:
2041 ptCount += 3;
2042 break;
2043 default: // kClose_Verb, just keep going
2044 break;
2045 }
2046 }
2047CONTOUR_END:
2048 fCurrPtCount = ptCount;
2049 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002050 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002051}
2052
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002053// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002054static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002055 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2056 // We may get 0 when the above subtracts underflow. We expect this to be
2057 // very rare and lazily promote to double.
2058 if (0 == cross) {
2059 double p0x = SkScalarToDouble(p0.fX);
2060 double p0y = SkScalarToDouble(p0.fY);
2061
2062 double p1x = SkScalarToDouble(p1.fX);
2063 double p1y = SkScalarToDouble(p1.fY);
2064
2065 double p2x = SkScalarToDouble(p2.fX);
2066 double p2y = SkScalarToDouble(p2.fY);
2067
2068 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2069 (p1y - p0y) * (p2x - p0x));
2070
2071 }
2072 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002073}
2074
reed@google.comc1ea60a2012-01-31 15:15:36 +00002075// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002076static int find_max_y(const SkPoint pts[], int count) {
2077 SkASSERT(count > 0);
2078 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002079 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002080 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002081 SkScalar y = pts[i].fY;
2082 if (y > max) {
2083 max = y;
2084 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002085 }
2086 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002087 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002088}
2089
reed@google.comcabaf1d2012-01-11 21:03:05 +00002090static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2091 int i = index;
2092 for (;;) {
2093 i = (i + inc) % n;
2094 if (i == index) { // we wrapped around, so abort
2095 break;
2096 }
2097 if (pts[index] != pts[i]) { // found a different point, success!
2098 break;
2099 }
2100 }
2101 return i;
2102}
2103
reed@google.comc1ea60a2012-01-31 15:15:36 +00002104/**
2105 * Starting at index, and moving forward (incrementing), find the xmin and
2106 * xmax of the contiguous points that have the same Y.
2107 */
2108static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2109 int* maxIndexPtr) {
2110 const SkScalar y = pts[index].fY;
2111 SkScalar min = pts[index].fX;
2112 SkScalar max = min;
2113 int minIndex = index;
2114 int maxIndex = index;
2115 for (int i = index + 1; i < n; ++i) {
2116 if (pts[i].fY != y) {
2117 break;
2118 }
2119 SkScalar x = pts[i].fX;
2120 if (x < min) {
2121 min = x;
2122 minIndex = i;
2123 } else if (x > max) {
2124 max = x;
2125 maxIndex = i;
2126 }
2127 }
2128 *maxIndexPtr = maxIndex;
2129 return minIndex;
2130}
2131
reed@google.comac8543f2012-01-30 20:51:25 +00002132static bool crossToDir(SkScalar cross, SkPath::Direction* dir) {
2133 if (dir) {
2134 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2135 }
2136 return true;
2137}
2138
reed@google.comc1ea60a2012-01-31 15:15:36 +00002139#if 0
2140#include "SkString.h"
2141#include "../utils/SkParsePath.h"
2142static void dumpPath(const SkPath& path) {
2143 SkString str;
2144 SkParsePath::ToSVGString(path, &str);
2145 SkDebugf("%s\n", str.c_str());
2146}
2147#endif
2148
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002149namespace {
2150// for use with convex_dir_test
2151double mul(double a, double b) { return a * b; }
2152SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2153double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2154SkScalar toScalar(SkScalar a) { return a; }
2155
2156// determines the winding direction of a convex polygon with the precision
2157// of T. CAST_SCALAR casts an SkScalar to T.
2158template <typename T, T (CAST_SCALAR)(SkScalar)>
2159bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2160 // we find the first three points that form a non-degenerate
2161 // triangle. If there are no such points then the path is
2162 // degenerate. The first is always point 0. Now we find the second
2163 // point.
2164 int i = 0;
2165 enum { kX = 0, kY = 1 };
2166 T v0[2];
2167 while (1) {
2168 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2169 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2170 if (v0[kX] || v0[kY]) {
2171 break;
2172 }
2173 if (++i == n - 1) {
2174 return false;
2175 }
2176 }
2177 // now find a third point that is not colinear with the first two
2178 // points and check the orientation of the triangle (which will be
2179 // the same as the orientation of the path).
2180 for (++i; i < n; ++i) {
2181 T v1[2];
2182 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2183 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2184 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2185 if (0 != cross) {
2186 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2187 return true;
2188 }
2189 }
2190 return false;
2191}
2192}
2193
reed@google.comac8543f2012-01-30 20:51:25 +00002194/*
2195 * We loop through all contours, and keep the computed cross-product of the
2196 * contour that contained the global y-max. If we just look at the first
2197 * contour, we may find one that is wound the opposite way (correctly) since
2198 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2199 * that is outer most (or at least has the global y-max) before we can consider
2200 * its cross product.
2201 */
reed@google.com69a99432012-01-10 18:00:10 +00002202bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002203// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002204 // don't want to pay the cost for computing this if it
2205 // is unknown, so we don't call isConvex()
2206 const Convexity conv = this->getConvexityOrUnknown();
2207
2208 ContourIter iter(fVerbs, fPts);
2209
reed@google.comac8543f2012-01-30 20:51:25 +00002210 // initialize with our logical y-min
2211 SkScalar ymax = this->getBounds().fTop;
2212 SkScalar ymaxCross = 0;
2213
reed@google.com69a99432012-01-10 18:00:10 +00002214 for (; !iter.done(); iter.next()) {
2215 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002216 if (n < 3) {
2217 continue;
2218 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002219
reed@google.comcabaf1d2012-01-11 21:03:05 +00002220 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002221 SkScalar cross = 0;
2222 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002223 // We try first at scalar precision, and then again at double
2224 // precision. This is because the vectors computed between distant
2225 // points may lose too much precision.
2226 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
2227 return true;
2228 }
2229 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
2230 return true;
2231 } else {
2232 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002233 }
2234 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002235 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002236 if (pts[index].fY < ymax) {
2237 continue;
2238 }
2239
reed@google.comc1ea60a2012-01-31 15:15:36 +00002240 // If there is more than 1 distinct point at the y-max, we take the
2241 // x-min and x-max of them and just subtract to compute the dir.
2242 if (pts[(index + 1) % n].fY == pts[index].fY) {
2243 int maxIndex;
2244 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002245 if (minIndex == maxIndex) {
2246 goto TRY_CROSSPROD;
2247 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002248 SkASSERT(pts[minIndex].fY == pts[index].fY);
2249 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2250 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2251 // we just subtract the indices, and let that auto-convert to
2252 // SkScalar, since we just want - or + to signal the direction.
2253 cross = minIndex - maxIndex;
2254 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002255 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002256 // Find a next and prev index to use for the cross-product test,
2257 // but we try to find pts that form non-zero vectors from pts[index]
2258 //
2259 // Its possible that we can't find two non-degenerate vectors, so
2260 // we have to guard our search (e.g. all the pts could be in the
2261 // same place).
2262
2263 // we pass n - 1 instead of -1 so we don't foul up % operator by
2264 // passing it a negative LH argument.
2265 int prev = find_diff_pt(pts, index, n, n - 1);
2266 if (prev == index) {
2267 // completely degenerate, skip to next contour
2268 continue;
2269 }
2270 int next = find_diff_pt(pts, index, n, 1);
2271 SkASSERT(next != index);
2272 cross = cross_prod(pts[prev], pts[index], pts[next]);
2273 // if we get a zero, but the pts aren't on top of each other, then
2274 // we can just look at the direction
2275 if (0 == cross) {
2276 // construct the subtract so we get the correct Direction below
2277 cross = pts[index].fX - pts[next].fX;
2278 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002279 }
reed@google.comac8543f2012-01-30 20:51:25 +00002280
2281 if (cross) {
2282 // record our best guess so far
2283 ymax = pts[index].fY;
2284 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002285 }
reed@google.com69a99432012-01-10 18:00:10 +00002286 }
2287 }
reed@google.com69a99432012-01-10 18:00:10 +00002288
reed@google.comac8543f2012-01-30 20:51:25 +00002289 return ymaxCross ? crossToDir(ymaxCross, dir) : false;
2290}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002291
2292///////////////////////////////////////////////////////////////////////////////
2293
2294static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2295 SkScalar D, SkScalar t) {
2296 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2297}
2298
2299static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2300 SkScalar t) {
2301 SkScalar A = c3 + 3*(c1 - c2) - c0;
2302 SkScalar B = 3*(c2 - c1 - c1 + c0);
2303 SkScalar C = 3*(c1 - c0);
2304 SkScalar D = c0;
2305 return eval_cubic_coeff(A, B, C, D, t);
2306}
2307
2308/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2309 t value such that cubic(t) = target
2310 */
2311static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2312 SkScalar target, SkScalar* t) {
2313 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2314 SkASSERT(c0 < target && target < c3);
2315
2316 SkScalar D = c0 - target;
2317 SkScalar A = c3 + 3*(c1 - c2) - c0;
2318 SkScalar B = 3*(c2 - c1 - c1 + c0);
2319 SkScalar C = 3*(c1 - c0);
2320
2321 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2322 SkScalar minT = 0;
2323 SkScalar maxT = SK_Scalar1;
2324 SkScalar mid;
2325 int i;
2326 for (i = 0; i < 16; i++) {
2327 mid = SkScalarAve(minT, maxT);
2328 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2329 if (delta < 0) {
2330 minT = mid;
2331 delta = -delta;
2332 } else {
2333 maxT = mid;
2334 }
2335 if (delta < TOLERANCE) {
2336 break;
2337 }
2338 }
2339 *t = mid;
2340 return true;
2341}
2342
2343template <size_t N> static void find_minmax(const SkPoint pts[],
2344 SkScalar* minPtr, SkScalar* maxPtr) {
2345 SkScalar min, max;
2346 min = max = pts[0].fX;
2347 for (size_t i = 1; i < N; ++i) {
2348 min = SkMinScalar(min, pts[i].fX);
2349 max = SkMaxScalar(max, pts[i].fX);
2350 }
2351 *minPtr = min;
2352 *maxPtr = max;
2353}
2354
2355static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2356 SkPoint storage[4];
2357
2358 int dir = 1;
2359 if (pts[0].fY > pts[3].fY) {
2360 storage[0] = pts[3];
2361 storage[1] = pts[2];
2362 storage[2] = pts[1];
2363 storage[3] = pts[0];
2364 pts = storage;
2365 dir = -1;
2366 }
2367 if (y < pts[0].fY || y >= pts[3].fY) {
2368 return 0;
2369 }
2370
2371 // quickreject or quickaccept
2372 SkScalar min, max;
2373 find_minmax<4>(pts, &min, &max);
2374 if (x < min) {
2375 return 0;
2376 }
2377 if (x > max) {
2378 return dir;
2379 }
2380
2381 // compute the actual x(t) value
2382 SkScalar t, xt;
2383 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2384 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2385 } else {
2386 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2387 xt = y < mid ? pts[0].fX : pts[3].fX;
2388 }
2389 return xt < x ? dir : 0;
2390}
2391
2392static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2393 SkPoint dst[10];
2394 int n = SkChopCubicAtYExtrema(pts, dst);
2395 int w = 0;
2396 for (int i = 0; i <= n; ++i) {
2397 w += winding_mono_cubic(&dst[i * 3], x, y);
2398 }
2399 return w;
2400}
2401
2402static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2403 SkScalar y0 = pts[0].fY;
2404 SkScalar y2 = pts[2].fY;
2405
2406 int dir = 1;
2407 if (y0 > y2) {
2408 SkTSwap(y0, y2);
2409 dir = -1;
2410 }
2411 if (y < y0 || y >= y2) {
2412 return 0;
2413 }
2414
2415 // bounds check on X (not required. is it faster?)
2416#if 0
2417 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2418 return 0;
2419 }
2420#endif
2421
2422 SkScalar roots[2];
2423 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2424 2 * (pts[1].fY - pts[0].fY),
2425 pts[0].fY - y,
2426 roots);
2427 SkASSERT(n <= 1);
2428 SkScalar xt;
2429 if (0 == n) {
2430 SkScalar mid = SkScalarAve(y0, y2);
2431 // Need [0] and [2] if dir == 1
2432 // and [2] and [0] if dir == -1
2433 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2434 } else {
2435 SkScalar t = roots[0];
2436 SkScalar C = pts[0].fX;
2437 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2438 SkScalar B = 2 * (pts[1].fX - C);
2439 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2440 }
2441 return xt < x ? dir : 0;
2442}
2443
2444static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2445 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2446 if (y0 == y1) {
2447 return true;
2448 }
2449 if (y0 < y1) {
2450 return y1 <= y2;
2451 } else {
2452 return y1 >= y2;
2453 }
2454}
2455
2456static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2457 SkPoint dst[5];
2458 int n = 0;
2459
2460 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2461 n = SkChopQuadAtYExtrema(pts, dst);
2462 pts = dst;
2463 }
2464 int w = winding_mono_quad(pts, x, y);
2465 if (n > 0) {
2466 w += winding_mono_quad(&pts[2], x, y);
2467 }
2468 return w;
2469}
2470
2471static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2472 SkScalar x0 = pts[0].fX;
2473 SkScalar y0 = pts[0].fY;
2474 SkScalar x1 = pts[1].fX;
2475 SkScalar y1 = pts[1].fY;
2476
2477 SkScalar dy = y1 - y0;
2478
2479 int dir = 1;
2480 if (y0 > y1) {
2481 SkTSwap(y0, y1);
2482 dir = -1;
2483 }
2484 if (y < y0 || y >= y1) {
2485 return 0;
2486 }
2487
2488 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2489 SkScalarMul(dy, x - pts[0].fX);
2490
2491 if (SkScalarSignAsInt(cross) == dir) {
2492 dir = 0;
2493 }
2494 return dir;
2495}
2496
2497bool SkPath::contains(SkScalar x, SkScalar y) const {
2498 bool isInverse = this->isInverseFillType();
2499 if (this->isEmpty()) {
2500 return isInverse;
2501 }
2502
2503 const SkRect& bounds = this->getBounds();
2504 if (!bounds.contains(x, y)) {
2505 return isInverse;
2506 }
2507
2508 SkPath::Iter iter(*this, true);
2509 bool done = false;
2510 int w = 0;
2511 do {
2512 SkPoint pts[4];
2513 switch (iter.next(pts, false)) {
2514 case SkPath::kMove_Verb:
2515 case SkPath::kClose_Verb:
2516 break;
2517 case SkPath::kLine_Verb:
2518 w += winding_line(pts, x, y);
2519 break;
2520 case SkPath::kQuad_Verb:
2521 w += winding_quad(pts, x, y);
2522 break;
2523 case SkPath::kCubic_Verb:
2524 w += winding_cubic(pts, x, y);
2525 break;
2526 case SkPath::kDone_Verb:
2527 done = true;
2528 break;
2529 }
2530 } while (!done);
2531
2532 switch (this->getFillType()) {
2533 case SkPath::kEvenOdd_FillType:
2534 case SkPath::kInverseEvenOdd_FillType:
2535 w &= 1;
2536 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002537 default:
2538 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002539 }
2540 return SkToBool(w);
2541}
2542