blob: 2e8837b06f49641c8c235def14bf9a13f0dfb025 [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
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com6aa29652012-04-18 13:29:52 +000045class SkAutoDisableOvalCheck {
46public:
47 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
48 fSaved = fPath->fIsOval;
49 }
50
51 ~SkAutoDisableOvalCheck() {
52 fPath->fIsOval = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 bool fSaved;
58};
59
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060class SkAutoDisableDirectionCheck {
61public:
62 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
63 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
64 }
65
66 ~SkAutoDisableDirectionCheck() {
67 fPath->fDirection = fSaved;
68 }
69
70private:
71 SkPath* fPath;
72 SkPath::Direction fSaved;
73};
74
reed@android.com8a1c16f2008-12-17 15:59:43 +000075/* This guy's constructor/destructor bracket a path editing operation. It is
76 used when we know the bounds of the amount we are going to add to the path
77 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000081 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000082
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 It also notes if the path was originally degenerate, and if so, sets
84 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.comca0c8382013-09-26 12:18:23 +000085 convex (which is always true since we only allow the addition of rects).
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 */
87class SkAutoPathBoundsUpdate {
88public:
89 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
90 this->init(path);
91 }
92
93 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
94 SkScalar right, SkScalar bottom) {
95 fRect.set(left, top, right, bottom);
96 this->init(path);
97 }
reed@google.comabf15c12011-01-18 20:35:51 +000098
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 fPath->setIsConvex(fDegenerate);
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000101 if (fEmpty || fHasValidBounds) {
102 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 }
104 }
reed@google.comabf15c12011-01-18 20:35:51 +0000105
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000107 SkPath* fPath;
108 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000109 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000110 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000111 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000112
reed@android.com6b82d1a2009-06-03 02:35:01 +0000113 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000114 // Cannot use fRect for our bounds unless we know it is sorted
115 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000117 // Mark the path's bounds as dirty if (1) they are, or (2) the path
118 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000119 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000121 if (fHasValidBounds && !fEmpty) {
122 joinNoEmptyChecks(&fRect, fPath->getBounds());
123 }
124 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 }
126};
127
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128////////////////////////////////////////////////////////////////////////////
129
130/*
131 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000132 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
134
135 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000136 1. If we encounter degenerate segments, remove them
137 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
138 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
139 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140*/
141
142////////////////////////////////////////////////////////////////////////////
143
reed@google.comd335d1d2012-01-12 18:17:11 +0000144// flag to require a moveTo if we begin with something else, like lineTo etc.
145#define INITIAL_LASTMOVETOINDEX_VALUE ~0
146
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000147SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000148 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149#ifdef SK_BUILD_FOR_ANDROID
150 , fGenerationID(0)
151#endif
152{
153 this->resetFields();
154}
155
156void SkPath::resetFields() {
157 //fPathRef is assumed to have been emptied by the caller.
158 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
159 fFillType = kWinding_FillType;
160 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000161 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000162 fDirection = kUnknown_Direction;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000163 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000164#ifdef SK_BUILD_FOR_ANDROID
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165 GEN_ID_INC;
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000166 // We don't touch fSourcePath. It's used to track texture garbage collection, so we don't
167 // want to muck with it if it's been set to something non-NULL.
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000168#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000169}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000170
bungeman@google.coma5809a32013-06-21 15:13:34 +0000171SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000172 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000174#ifdef SK_BUILD_FOR_ANDROID
175 fGenerationID = that.fGenerationID;
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000176 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000177#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179}
180
181SkPath::~SkPath() {
182 SkDEBUGCODE(this->validate();)
183}
184
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185SkPath& SkPath::operator=(const SkPath& that) {
186 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000187
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 if (this != &that) {
189 fPathRef.reset(SkRef(that.fPathRef.get()));
190 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000191#ifdef SK_BUILD_FOR_ANDROID
192 GEN_ID_INC; // Similar to swap, we can't just copy this or it could go back in time.
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000193 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000194#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 }
196 SkDEBUGCODE(this->validate();)
197 return *this;
198}
199
bungeman@google.coma5809a32013-06-21 15:13:34 +0000200void SkPath::copyFields(const SkPath& that) {
201 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202 fLastMoveToIndex = that.fLastMoveToIndex;
203 fFillType = that.fFillType;
204 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205 fConvexity = that.fConvexity;
206 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000207 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000208}
209
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000210bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000211 // note: don't need to look at isConvex or bounds, since just comparing the
212 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000213
214 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
215 // since it is only a cache of info in the fVerbs, but its a fast way to
216 // notice a difference
217
reed@android.com3abec1d2009-03-02 05:36:20 +0000218 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000219 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000220 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000221}
222
bungeman@google.coma5809a32013-06-21 15:13:34 +0000223void SkPath::swap(SkPath& that) {
224 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225
bungeman@google.coma5809a32013-06-21 15:13:34 +0000226 if (this != &that) {
227 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000228 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
229 SkTSwap<uint8_t>(fFillType, that.fFillType);
230 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000231 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
232 SkTSwap<uint8_t>(fDirection, that.fDirection);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000233 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000234#ifdef SK_BUILD_FOR_ANDROID
235 // It doesn't really make sense to swap the generation IDs here, because they might go
236 // backwards. To be safe we increment both to mark them both as changed.
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000237 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000238 GEN_ID_PTR_INC(&that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000239 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
240#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000241 }
242}
243
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000244static inline bool check_edge_against_rect(const SkPoint& p0,
245 const SkPoint& p1,
246 const SkRect& rect,
247 SkPath::Direction dir) {
248 const SkPoint* edgeBegin;
249 SkVector v;
250 if (SkPath::kCW_Direction == dir) {
251 v = p1 - p0;
252 edgeBegin = &p0;
253 } else {
254 v = p0 - p1;
255 edgeBegin = &p1;
256 }
257 if (v.fX || v.fY) {
258 // check the cross product of v with the vec from edgeBegin to each rect corner
259 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
260 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
261 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
262 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
263 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
264 return false;
265 }
266 }
267 return true;
268}
269
270bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
271 // This only handles non-degenerate convex paths currently.
272 if (kConvex_Convexity != this->getConvexity()) {
273 return false;
274 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000275
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000276 Direction direction;
277 if (!this->cheapComputeDirection(&direction)) {
278 return false;
279 }
280
281 SkPoint firstPt;
282 SkPoint prevPt;
283 RawIter iter(*this);
284 SkPath::Verb verb;
285 SkPoint pts[4];
286 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkDEBUGCODE(int segmentCount = 0;)
288 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289
290 while ((verb = iter.next(pts)) != kDone_Verb) {
291 int nextPt = -1;
292 switch (verb) {
293 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000294 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 SkDEBUGCODE(++moveCnt);
296 firstPt = prevPt = pts[0];
297 break;
298 case kLine_Verb:
299 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000300 SkASSERT(moveCnt && !closeCount);
301 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000302 break;
303 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000304 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000305 SkASSERT(moveCnt && !closeCount);
306 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000307 nextPt = 2;
308 break;
309 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000310 SkASSERT(moveCnt && !closeCount);
311 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000312 nextPt = 3;
313 break;
314 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000315 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000316 break;
317 default:
318 SkDEBUGFAIL("unknown verb");
319 }
320 if (-1 != nextPt) {
321 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
322 return false;
323 }
324 prevPt = pts[nextPt];
325 }
326 }
327
328 return check_edge_against_rect(prevPt, firstPt, rect, direction);
329}
330
djsollen@google.com56c69772011-11-08 19:00:26 +0000331#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000332uint32_t SkPath::getGenerationID() const {
333 return fGenerationID;
334}
djsollen@google.come63793a2012-03-21 15:39:03 +0000335
336const SkPath* SkPath::getSourcePath() const {
337 return fSourcePath;
338}
339
340void SkPath::setSourcePath(const SkPath* path) {
341 fSourcePath = path;
342}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000343#endif
344
reed@android.com8a1c16f2008-12-17 15:59:43 +0000345void SkPath::reset() {
346 SkDEBUGCODE(this->validate();)
347
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000348 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000349 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350}
351
352void SkPath::rewind() {
353 SkDEBUGCODE(this->validate();)
354
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000355 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000356 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000357}
358
reed@google.com7e6c4d12012-05-10 14:05:43 +0000359bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000360 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000361
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000362 if (2 == verbCount) {
363 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
364 if (kLine_Verb == fPathRef->atVerb(1)) {
365 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000366 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000367 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000368 line[0] = pts[0];
369 line[1] = pts[1];
370 }
371 return true;
372 }
373 }
374 return false;
375}
376
caryclark@google.comf1316942011-07-26 19:54:45 +0000377/*
378 Determines if path is a rect by keeping track of changes in direction
379 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000380
caryclark@google.comf1316942011-07-26 19:54:45 +0000381 The direction is computed such that:
382 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000383 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000384 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000385 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000386
caryclark@google.comf1316942011-07-26 19:54:45 +0000387A rectangle cycles up/right/down/left or up/left/down/right.
388
389The test fails if:
390 The path is closed, and followed by a line.
391 A second move creates a new endpoint.
392 A diagonal line is parsed.
393 There's more than four changes of direction.
394 There's a discontinuity on the line (e.g., a move in the middle)
395 The line reverses direction.
396 The rectangle doesn't complete a cycle.
397 The path contains a quadratic or cubic.
398 The path contains fewer than four points.
399 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000400
caryclark@google.comf1316942011-07-26 19:54:45 +0000401It's OK if the path has:
402 Several colinear line segments composing a rectangle side.
403 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405The direction takes advantage of the corners found since opposite sides
406must travel in opposite directions.
407
408FIXME: Allow colinear quads and cubics to be treated like lines.
409FIXME: If the API passes fill-only, return true if the filled stroke
410 is a rectangle, though the caller failed to close the path.
411 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000412bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
413 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 int corners = 0;
415 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000416 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000417 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000418 first.set(0, 0);
419 last.set(0, 0);
420 int firstDirection = 0;
421 int lastDirection = 0;
422 int nextDirection = 0;
423 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000424 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000425 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000426 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
427 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000429 savePts = pts;
430 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000431 autoClose = true;
432 case kLine_Verb: {
433 SkScalar left = last.fX;
434 SkScalar top = last.fY;
435 SkScalar right = pts->fX;
436 SkScalar bottom = pts->fY;
437 ++pts;
438 if (left != right && top != bottom) {
439 return false; // diagonal
440 }
441 if (left == right && top == bottom) {
442 break; // single point on side OK
443 }
444 nextDirection = (left != right) << 0 |
445 (left < right || top < bottom) << 1;
446 if (0 == corners) {
447 firstDirection = nextDirection;
448 first = last;
449 last = pts[-1];
450 corners = 1;
451 closedOrMoved = false;
452 break;
453 }
454 if (closedOrMoved) {
455 return false; // closed followed by a line
456 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000457 if (autoClose && nextDirection == firstDirection) {
458 break; // colinear with first
459 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 closedOrMoved = autoClose;
461 if (lastDirection != nextDirection) {
462 if (++corners > 4) {
463 return false; // too many direction changes
464 }
465 }
466 last = pts[-1];
467 if (lastDirection == nextDirection) {
468 break; // colinear segment
469 }
470 // Possible values for corners are 2, 3, and 4.
471 // When corners == 3, nextDirection opposes firstDirection.
472 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000473 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
475 if ((directionCycle ^ turn) != nextDirection) {
476 return false; // direction didn't follow cycle
477 }
478 break;
479 }
480 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000481 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 case kCubic_Verb:
483 return false; // quadratic, cubic not allowed
484 case kMove_Verb:
485 last = *pts++;
486 closedOrMoved = true;
487 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000488 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000489 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000490 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000492 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 lastDirection = nextDirection;
494 }
495 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000496 bool result = 4 == corners && (first == last || autoClose);
497 if (savePts) {
498 *ptsPtr = savePts;
499 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000500 if (result && isClosed) {
501 *isClosed = autoClose;
502 }
503 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000504 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000505 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000506 return result;
507}
508
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000509bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000510 SkDEBUGCODE(this->validate();)
511 int currVerb = 0;
512 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000513 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
514 if (result && rect) {
515 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000517 return result;
518}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000519
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000520bool SkPath::isRect(bool* isClosed, Direction* direction) const {
521 SkDEBUGCODE(this->validate();)
522 int currVerb = 0;
523 const SkPoint* pts = fPathRef->points();
524 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000525}
526
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000527bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000528 SkDEBUGCODE(this->validate();)
529 int currVerb = 0;
530 const SkPoint* pts = fPathRef->points();
531 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000532 Direction testDirs[2];
533 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 return false;
535 }
536 const SkPoint* last = pts;
537 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000538 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000539 testRects[0].set(first, SkToS32(last - first));
540 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000541 if (testRects[0].contains(testRects[1])) {
542 if (rects) {
543 rects[0] = testRects[0];
544 rects[1] = testRects[1];
545 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000546 if (dirs) {
547 dirs[0] = testDirs[0];
548 dirs[1] = testDirs[1];
549 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 return true;
551 }
552 if (testRects[1].contains(testRects[0])) {
553 if (rects) {
554 rects[0] = testRects[1];
555 rects[1] = testRects[0];
556 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000557 if (dirs) {
558 dirs[0] = testDirs[1];
559 dirs[1] = testDirs[0];
560 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 return true;
562 }
563 }
564 return false;
565}
566
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000567int SkPath::countPoints() const {
568 return fPathRef->countPoints();
569}
570
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000571int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000572 SkDEBUGCODE(this->validate();)
573
574 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000575 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576 int count = SkMin32(max, fPathRef->countPoints());
577 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
578 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000579}
580
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000581SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000582 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
583 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000584 }
585 return SkPoint::Make(0, 0);
586}
587
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000588int SkPath::countVerbs() const {
589 return fPathRef->countVerbs();
590}
591
592static inline void copy_verbs_reverse(uint8_t* inorderDst,
593 const uint8_t* reversedSrc,
594 int count) {
595 for (int i = 0; i < count; ++i) {
596 inorderDst[i] = reversedSrc[~i];
597 }
598}
599
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000600int SkPath::getVerbs(uint8_t dst[], int max) const {
601 SkDEBUGCODE(this->validate();)
602
603 SkASSERT(max >= 0);
604 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000605 int count = SkMin32(max, fPathRef->countVerbs());
606 copy_verbs_reverse(dst, fPathRef->verbs(), count);
607 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000608}
609
reed@google.com294dd7b2011-10-11 11:58:32 +0000610bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000611 SkDEBUGCODE(this->validate();)
612
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000614 if (count > 0) {
615 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000616 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000618 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000619 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000620 if (lastPt) {
621 lastPt->set(0, 0);
622 }
623 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624}
625
626void SkPath::setLastPt(SkScalar x, SkScalar y) {
627 SkDEBUGCODE(this->validate();)
628
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000629 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000630 if (count == 0) {
631 this->moveTo(x, y);
632 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000633 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634 SkPathRef::Editor ed(&fPathRef);
635 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000636 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 }
638}
639
reed@google.com04863fa2011-05-15 04:08:24 +0000640void SkPath::setConvexity(Convexity c) {
641 if (fConvexity != c) {
642 fConvexity = c;
643 GEN_ID_INC;
644 }
645}
646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647//////////////////////////////////////////////////////////////////////////////
648// Construction methods
649
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000650#define DIRTY_AFTER_EDIT \
651 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000652 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000653 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000654 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000655 } while (0)
656
reed@android.com8a1c16f2008-12-17 15:59:43 +0000657void SkPath::incReserve(U16CPU inc) {
658 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 SkDEBUGCODE(this->validate();)
661}
662
663void SkPath::moveTo(SkScalar x, SkScalar y) {
664 SkDEBUGCODE(this->validate();)
665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667
reed@google.comd335d1d2012-01-12 18:17:11 +0000668 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000669 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000673 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674}
675
676void SkPath::rMoveTo(SkScalar x, SkScalar y) {
677 SkPoint pt;
678 this->getLastPt(&pt);
679 this->moveTo(pt.fX + x, pt.fY + y);
680}
681
reed@google.comd335d1d2012-01-12 18:17:11 +0000682void SkPath::injectMoveToIfNeeded() {
683 if (fLastMoveToIndex < 0) {
684 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000686 x = y = 0;
687 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000689 x = pt.fX;
690 y = pt.fY;
691 }
692 this->moveTo(x, y);
693 }
694}
695
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696void SkPath::lineTo(SkScalar x, SkScalar y) {
697 SkDEBUGCODE(this->validate();)
698
reed@google.comd335d1d2012-01-12 18:17:11 +0000699 this->injectMoveToIfNeeded();
700
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000701 SkPathRef::Editor ed(&fPathRef);
702 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000703 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000705 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000706 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707}
708
709void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000710 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711 SkPoint pt;
712 this->getLastPt(&pt);
713 this->lineTo(pt.fX + x, pt.fY + y);
714}
715
716void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
717 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000718
reed@google.comd335d1d2012-01-12 18:17:11 +0000719 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000720
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 SkPathRef::Editor ed(&fPathRef);
722 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 pts[0].set(x1, y1);
724 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000725 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000726
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000727 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000728 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729}
730
731void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000732 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733 SkPoint pt;
734 this->getLastPt(&pt);
735 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
736}
737
reed@google.com277c3f82013-05-31 15:17:50 +0000738void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
739 SkScalar w) {
740 // check for <= 0 or NaN with this test
741 if (!(w > 0)) {
742 this->lineTo(x2, y2);
743 } else if (!SkScalarIsFinite(w)) {
744 this->lineTo(x1, y1);
745 this->lineTo(x2, y2);
746 } else if (SK_Scalar1 == w) {
747 this->quadTo(x1, y1, x2, y2);
748 } else {
749 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000750
reed@google.com277c3f82013-05-31 15:17:50 +0000751 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000752
reed@google.com277c3f82013-05-31 15:17:50 +0000753 SkPathRef::Editor ed(&fPathRef);
754 SkPoint* pts = ed.growForConic(w);
755 pts[0].set(x1, y1);
756 pts[1].set(x2, y2);
757 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000758
reed@google.com277c3f82013-05-31 15:17:50 +0000759 GEN_ID_INC;
760 DIRTY_AFTER_EDIT;
761 }
762}
763
764void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
765 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000766 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000767 SkPoint pt;
768 this->getLastPt(&pt);
769 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
770}
771
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
773 SkScalar x3, SkScalar y3) {
774 SkDEBUGCODE(this->validate();)
775
reed@google.comd335d1d2012-01-12 18:17:11 +0000776 this->injectMoveToIfNeeded();
777
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000778 SkPathRef::Editor ed(&fPathRef);
779 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780 pts[0].set(x1, y1);
781 pts[1].set(x2, y2);
782 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000783 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000785 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000786 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787}
788
789void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
790 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000791 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 SkPoint pt;
793 this->getLastPt(&pt);
794 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
795 pt.fX + x3, pt.fY + y3);
796}
797
798void SkPath::close() {
799 SkDEBUGCODE(this->validate();)
800
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000801 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000802 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000803 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 case kLine_Verb:
805 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000806 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000808 case kMove_Verb: {
809 SkPathRef::Editor ed(&fPathRef);
810 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000811 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 }
reed@google.com277c3f82013-05-31 15:17:50 +0000814 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000815 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000816 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000817 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000818 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000819 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 }
821 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000822
823 // signal that we need a moveTo to follow us (unless we're done)
824#if 0
825 if (fLastMoveToIndex >= 0) {
826 fLastMoveToIndex = ~fLastMoveToIndex;
827 }
828#else
829 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
830#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831}
832
833///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000834
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000835static void assert_known_direction(int dir) {
836 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
837}
838
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839void SkPath::addRect(const SkRect& rect, Direction dir) {
840 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
841}
842
843void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
844 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000845 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000846 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
847 SkAutoDisableDirectionCheck addc(this);
848
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
850
851 this->incReserve(5);
852
853 this->moveTo(left, top);
854 if (dir == kCCW_Direction) {
855 this->lineTo(left, bottom);
856 this->lineTo(right, bottom);
857 this->lineTo(right, top);
858 } else {
859 this->lineTo(right, top);
860 this->lineTo(right, bottom);
861 this->lineTo(left, bottom);
862 }
863 this->close();
864}
865
reed@google.com744faba2012-05-29 19:54:52 +0000866void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
867 SkDEBUGCODE(this->validate();)
868 if (count <= 0) {
869 return;
870 }
871
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000872 SkPathRef::Editor ed(&fPathRef);
873 fLastMoveToIndex = ed.pathRef()->countPoints();
874 uint8_t* vb;
875 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000876 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000878
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 memcpy(p, pts, count * sizeof(SkPoint));
880 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000881 if (count > 1) {
882 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
883 // be 0, the compiler will remove the test/branch entirely.
884 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000885 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000886 } else {
887 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000889 }
890 }
891 fSegmentMask |= kLine_SegmentMask;
892 }
893 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000894 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000895 }
896
897 GEN_ID_INC;
898 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000899 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000900}
901
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000902#include "SkGeometry.h"
903
904static int build_arc_points(const SkRect& oval, SkScalar startAngle,
905 SkScalar sweepAngle,
906 SkPoint pts[kSkBuildQuadArcStorage]) {
907
908 if (0 == sweepAngle &&
909 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
910 // Chrome uses this path to move into and out of ovals. If not
911 // treated as a special case the moves can distort the oval's
912 // bounding box (and break the circle special case).
913 pts[0].set(oval.fRight, oval.centerY());
914 return 1;
915 } else if (0 == oval.width() && 0 == oval.height()) {
916 // Chrome will sometimes create 0 radius round rects. Having degenerate
917 // quad segments in the path prevents the path from being recognized as
918 // a rect.
919 // TODO: optimizing the case where only one of width or height is zero
920 // should also be considered. This case, however, doesn't seem to be
921 // as common as the single point case.
922 pts[0].set(oval.fRight, oval.fTop);
923 return 1;
924 }
925
926 SkVector start, stop;
927
928 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
929 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
930 &stop.fX);
931
932 /* If the sweep angle is nearly (but less than) 360, then due to precision
933 loss in radians-conversion and/or sin/cos, we may end up with coincident
934 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
935 of drawing a nearly complete circle (good).
936 e.g. canvas.drawArc(0, 359.99, ...)
937 -vs- canvas.drawArc(0, 359.9, ...)
938 We try to detect this edge case, and tweak the stop vector
939 */
940 if (start == stop) {
941 SkScalar sw = SkScalarAbs(sweepAngle);
942 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
943 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
944 // make a guess at a tiny angle (in radians) to tweak by
945 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
946 // not sure how much will be enough, so we use a loop
947 do {
948 stopRad -= deltaRad;
949 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
950 } while (start == stop);
951 }
952 }
953
954 SkMatrix matrix;
955
956 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
957 matrix.postTranslate(oval.centerX(), oval.centerY());
958
959 return SkBuildQuadArc(start, stop,
960 sweepAngle > 0 ? kCW_SkRotationDirection :
961 kCCW_SkRotationDirection,
962 &matrix, pts);
963}
964
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965static void add_corner_arc(SkPath* path, const SkRect& rect,
966 SkScalar rx, SkScalar ry, int startAngle,
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000967 SkPath::Direction dir, bool addArcTo,
968 bool forceMoveTo = false) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000969 // These two asserts are not sufficient, since really we want to know
970 // that the pair of radii (e.g. left and right, or top and bottom) sum
971 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000972 // these conservative asserts.
973 SkASSERT(0 <= rx && rx <= rect.width());
974 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000975
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 SkRect r;
977 r.set(-rx, -ry, rx, ry);
978
979 switch (startAngle) {
980 case 0:
981 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
982 break;
983 case 90:
984 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
985 break;
986 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
987 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000988 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 }
reed@google.comabf15c12011-01-18 20:35:51 +0000990
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991 SkScalar start = SkIntToScalar(startAngle);
992 SkScalar sweep = SkIntToScalar(90);
993 if (SkPath::kCCW_Direction == dir) {
994 start += sweep;
995 sweep = -sweep;
996 }
reed@google.comabf15c12011-01-18 20:35:51 +0000997
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000998 if (addArcTo) {
999 path->arcTo(r, start, sweep, forceMoveTo);
1000 } else {
1001 SkPoint pts[kSkBuildQuadArcStorage];
1002 int count = build_arc_points(r, start, sweep, pts);
1003 for (int i = 1; i < count; i += 2) {
1004 path->quadTo(pts[i], pts[i+1]);
1005 }
1006 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007}
1008
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001009void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001011 SkRRect rrect;
1012 rrect.setRectRadii(rect, (const SkVector*) radii);
1013 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014}
1015
reed@google.com4ed0fb72012-12-12 20:48:18 +00001016void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001017 assert_known_direction(dir);
1018
1019 if (rrect.isEmpty()) {
1020 return;
1021 }
1022
reed@google.com4ed0fb72012-12-12 20:48:18 +00001023 const SkRect& bounds = rrect.getBounds();
1024
1025 if (rrect.isRect()) {
1026 this->addRect(bounds, dir);
1027 } else if (rrect.isOval()) {
1028 this->addOval(bounds, dir);
1029 } else if (rrect.isSimple()) {
1030 const SkVector& rad = rrect.getSimpleRadii();
1031 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1032 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001033 SkAutoPathBoundsUpdate apbu(this, bounds);
1034
1035 if (kCW_Direction == dir) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001036 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true, true);
1037 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, true);
1038 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, true);
1039 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, true);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001040 } else {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true, true);
1042 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, true);
1043 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, true);
1044 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, true);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001045 }
1046 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001047 }
1048}
1049
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001050bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001051 int count = fPathRef->countVerbs();
1052 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1053 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001054 if (*verbs == kLine_Verb ||
1055 *verbs == kQuad_Verb ||
1056 *verbs == kCubic_Verb) {
1057 return false;
1058 }
1059 ++verbs;
1060 }
1061 return true;
1062}
1063
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001065#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001066#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001067
1068void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1069 Direction dir) {
1070 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001071
humper@google.com75e3ca12013-04-08 21:44:11 +00001072 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001073 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001074 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001075 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001076 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1077 return;
1078 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001079
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001080 SkScalar w = rect.width();
1081 SkScalar halfW = SkScalarHalf(w);
1082 SkScalar h = rect.height();
1083 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001084
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001085 if (halfW <= 0 || halfH <= 0) {
1086 return;
1087 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001088
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001089 bool skip_hori = rx >= halfW;
1090 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001091
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001092 if (skip_hori && skip_vert) {
1093 this->addOval(rect, dir);
1094 return;
1095 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001096
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001097 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001098
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001099 SkAutoPathBoundsUpdate apbu(this, rect);
1100 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001101
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001102 if (skip_hori) {
1103 rx = halfW;
1104 } else if (skip_vert) {
1105 ry = halfH;
1106 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001107
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001108#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001109 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1110 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001111
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001112 this->incReserve(17);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001113#else
1114 this->incReserve(13);
1115#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001116 this->moveTo(rect.fRight - rx, rect.fTop);
1117 if (dir == kCCW_Direction) {
1118 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001119 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001120 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001121#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001122 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1123 rect.fLeft, rect.fTop + ry - sy,
1124 rect.fLeft, rect.fTop + ry); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001125#else
1126 add_corner_arc(this, rect, rx, ry, 180, dir, false); // top-left
1127#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001128 if (!skip_vert) {
1129 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1130 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001131#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001132 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1133 rect.fLeft + rx - sx, rect.fBottom,
1134 rect.fLeft + rx, rect.fBottom); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001135#else
1136 add_corner_arc(this, rect, rx, ry, 90, dir, false); // bot-left
1137#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001138 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001139 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001140 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001141#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001142 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1143 rect.fRight, rect.fBottom - ry + sy,
1144 rect.fRight, rect.fBottom - ry); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001145#else
1146 add_corner_arc(this, rect, rx, ry, 0, dir, false); // bot-right
1147#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001148 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001149 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001150 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001151#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001152 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1153 rect.fRight - rx + sx, rect.fTop,
1154 rect.fRight - rx, rect.fTop); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001155#else
1156 add_corner_arc(this, rect, rx, ry, 270, dir, false); // top-right
1157#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001158 } else {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001159#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001160 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1161 rect.fRight, rect.fTop + ry - sy,
1162 rect.fRight, rect.fTop + ry); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001163#else
1164 add_corner_arc(this, rect, rx, ry, 270, dir, false); // top-right
1165#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001166 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001167 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001168 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001169#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001170 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1171 rect.fRight - rx + sx, rect.fBottom,
1172 rect.fRight - rx, rect.fBottom); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001173#else
1174 add_corner_arc(this, rect, rx, ry, 0, dir, false); // bot-right
1175#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001176 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001177 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001178 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001179#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001180 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1181 rect.fLeft, rect.fBottom - ry + sy,
1182 rect.fLeft, rect.fBottom - ry); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001183#else
1184 add_corner_arc(this, rect, rx, ry, 90, dir, false); // bot-left
1185#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001186 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001187 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001188 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001189#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001190 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1191 rect.fLeft + rx - sx, rect.fTop,
1192 rect.fLeft + rx, rect.fTop); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001193#else
1194 add_corner_arc(this, rect, rx, ry, 180, dir, false); // top-left
1195#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001196 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001197 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198 }
1199 }
1200 this->close();
1201}
1202
reed@android.com8a1c16f2008-12-17 15:59:43 +00001203void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001204 assert_known_direction(dir);
1205
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001206 /* If addOval() is called after previous moveTo(),
1207 this path is still marked as an oval. This is used to
1208 fit into WebKit's calling sequences.
1209 We can't simply check isEmpty() in this case, as additional
1210 moveTo() would mark the path non empty.
1211 */
1212 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001213 if (fIsOval) {
1214 fDirection = dir;
1215 } else {
1216 fDirection = kUnknown_Direction;
1217 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001218
1219 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001220 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001221
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 SkAutoPathBoundsUpdate apbu(this, oval);
1223
1224 SkScalar cx = oval.centerX();
1225 SkScalar cy = oval.centerY();
1226 SkScalar rx = SkScalarHalf(oval.width());
1227 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1230 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1231 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1232 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1233
1234 /*
1235 To handle imprecision in computing the center and radii, we revert to
1236 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1237 to ensure that we don't exceed the oval's bounds *ever*, since we want
1238 to use oval for our fast-bounds, rather than have to recompute it.
1239 */
1240 const SkScalar L = oval.fLeft; // cx - rx
1241 const SkScalar T = oval.fTop; // cy - ry
1242 const SkScalar R = oval.fRight; // cx + rx
1243 const SkScalar B = oval.fBottom; // cy + ry
1244
1245 this->incReserve(17); // 8 quads + close
1246 this->moveTo(R, cy);
1247 if (dir == kCCW_Direction) {
1248 this->quadTo( R, cy - sy, cx + mx, cy - my);
1249 this->quadTo(cx + sx, T, cx , T);
1250 this->quadTo(cx - sx, T, cx - mx, cy - my);
1251 this->quadTo( L, cy - sy, L, cy );
1252 this->quadTo( L, cy + sy, cx - mx, cy + my);
1253 this->quadTo(cx - sx, B, cx , B);
1254 this->quadTo(cx + sx, B, cx + mx, cy + my);
1255 this->quadTo( R, cy + sy, R, cy );
1256 } else {
1257 this->quadTo( R, cy + sy, cx + mx, cy + my);
1258 this->quadTo(cx + sx, B, cx , B);
1259 this->quadTo(cx - sx, B, cx - mx, cy + my);
1260 this->quadTo( L, cy + sy, L, cy );
1261 this->quadTo( L, cy - sy, cx - mx, cy - my);
1262 this->quadTo(cx - sx, T, cx , T);
1263 this->quadTo(cx + sx, T, cx + mx, cy - my);
1264 this->quadTo( R, cy - sy, R, cy );
1265 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001266 this->close();
1267}
1268
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001269bool SkPath::isOval(SkRect* rect) const {
1270 if (fIsOval && rect) {
1271 *rect = getBounds();
1272 }
1273
1274 return fIsOval;
1275}
1276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1278 if (r > 0) {
1279 SkRect rect;
1280 rect.set(x - r, y - r, x + r, y + r);
1281 this->addOval(rect, dir);
1282 }
1283}
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1286 bool forceMoveTo) {
1287 if (oval.width() < 0 || oval.height() < 0) {
1288 return;
1289 }
1290
1291 SkPoint pts[kSkBuildQuadArcStorage];
1292 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1293 SkASSERT((count & 1) == 1);
1294
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001295 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 forceMoveTo = true;
1297 }
1298 this->incReserve(count);
1299 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1300 for (int i = 1; i < count; i += 2) {
1301 this->quadTo(pts[i], pts[i+1]);
1302 }
1303}
1304
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001305void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306 if (oval.isEmpty() || 0 == sweepAngle) {
1307 return;
1308 }
1309
1310 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1311
1312 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1313 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1314 return;
1315 }
1316
1317 SkPoint pts[kSkBuildQuadArcStorage];
1318 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1319
1320 this->incReserve(count);
1321 this->moveTo(pts[0]);
1322 for (int i = 1; i < count; i += 2) {
1323 this->quadTo(pts[i], pts[i+1]);
1324 }
1325}
1326
1327/*
1328 Need to handle the case when the angle is sharp, and our computed end-points
1329 for the arc go behind pt1 and/or p2...
1330*/
1331void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1332 SkScalar radius) {
1333 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001334
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 // need to know our prev pt so we can construct tangent vectors
1336 {
1337 SkPoint start;
1338 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001339 // Handle degenerate cases by adding a line to the first point and
1340 // bailing out.
1341 if ((x1 == start.fX && y1 == start.fY) ||
1342 (x1 == x2 && y1 == y2) ||
1343 radius == 0) {
1344 this->lineTo(x1, y1);
1345 return;
1346 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001347 before.setNormalize(x1 - start.fX, y1 - start.fY);
1348 after.setNormalize(x2 - x1, y2 - y1);
1349 }
reed@google.comabf15c12011-01-18 20:35:51 +00001350
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351 SkScalar cosh = SkPoint::DotProduct(before, after);
1352 SkScalar sinh = SkPoint::CrossProduct(before, after);
1353
1354 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001355 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 return;
1357 }
reed@google.comabf15c12011-01-18 20:35:51 +00001358
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1360 if (dist < 0) {
1361 dist = -dist;
1362 }
1363
1364 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1365 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1366 SkRotationDirection arcDir;
1367
1368 // now turn before/after into normals
1369 if (sinh > 0) {
1370 before.rotateCCW();
1371 after.rotateCCW();
1372 arcDir = kCW_SkRotationDirection;
1373 } else {
1374 before.rotateCW();
1375 after.rotateCW();
1376 arcDir = kCCW_SkRotationDirection;
1377 }
1378
1379 SkMatrix matrix;
1380 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001381
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 matrix.setScale(radius, radius);
1383 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1384 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001385
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001387
reed@android.com8a1c16f2008-12-17 15:59:43 +00001388 this->incReserve(count);
1389 // [xx,yy] == pts[0]
1390 this->lineTo(xx, yy);
1391 for (int i = 1; i < count; i += 2) {
1392 this->quadTo(pts[i], pts[i+1]);
1393 }
1394}
1395
1396///////////////////////////////////////////////////////////////////////////////
1397
1398void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1399 SkMatrix matrix;
1400
1401 matrix.setTranslate(dx, dy);
1402 this->addPath(path, matrix);
1403}
1404
1405void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001406 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001408 fIsOval = false;
1409
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001410 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 SkPoint pts[4];
1412 Verb verb;
1413
1414 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1415
1416 while ((verb = iter.next(pts)) != kDone_Verb) {
1417 switch (verb) {
1418 case kMove_Verb:
1419 proc(matrix, &pts[0], &pts[0], 1);
1420 this->moveTo(pts[0]);
1421 break;
1422 case kLine_Verb:
1423 proc(matrix, &pts[1], &pts[1], 1);
1424 this->lineTo(pts[1]);
1425 break;
1426 case kQuad_Verb:
1427 proc(matrix, &pts[1], &pts[1], 2);
1428 this->quadTo(pts[1], pts[2]);
1429 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001430 case kConic_Verb:
1431 proc(matrix, &pts[1], &pts[1], 2);
1432 this->conicTo(pts[1], pts[2], iter.conicWeight());
1433 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 case kCubic_Verb:
1435 proc(matrix, &pts[1], &pts[1], 3);
1436 this->cubicTo(pts[1], pts[2], pts[3]);
1437 break;
1438 case kClose_Verb:
1439 this->close();
1440 break;
1441 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001442 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 }
1444 }
1445}
1446
1447///////////////////////////////////////////////////////////////////////////////
1448
reed@google.com277c3f82013-05-31 15:17:50 +00001449static int pts_in_verb(unsigned verb) {
1450 static const uint8_t gPtsInVerb[] = {
1451 1, // kMove
1452 1, // kLine
1453 2, // kQuad
1454 2, // kConic
1455 3, // kCubic
1456 0, // kClose
1457 0 // kDone
1458 };
1459
1460 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1461 return gPtsInVerb[verb];
1462}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463
1464// ignore the initial moveto, and stop when the 1st contour ends
1465void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001466 int i, vcount = path.fPathRef->countVerbs();
1467 // exit early if the path is empty, or just has a moveTo.
1468 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 return;
1470 }
1471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001474 fIsOval = false;
1475
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001476 const uint8_t* verbs = path.fPathRef->verbs();
1477 // skip the initial moveTo
1478 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001479 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001481 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001484 case kLine_Verb:
1485 this->lineTo(pts[0].fX, pts[0].fY);
1486 break;
1487 case kQuad_Verb:
1488 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1489 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001490 case kConic_Verb:
1491 this->conicTo(pts[0], pts[1], *conicWeight++);
1492 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001494 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 break;
1496 case kClose_Verb:
1497 return;
1498 }
reed@google.com277c3f82013-05-31 15:17:50 +00001499 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 }
1501}
1502
1503// ignore the last point of the 1st contour
1504void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001505 int i, vcount = path.fPathRef->countVerbs();
1506 // exit early if the path is empty, or just has a moveTo.
1507 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 return;
1509 }
1510
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001511 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001513 fIsOval = false;
1514
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001515 const uint8_t* verbs = path.fPathRef->verbs();
1516 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001517 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 SkASSERT(verbs[~0] == kMove_Verb);
1520 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001521 unsigned v = verbs[~i];
1522 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 if (n == 0) {
1524 break;
1525 }
1526 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001527 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 }
1529
1530 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 case kLine_Verb:
1533 this->lineTo(pts[-1].fX, pts[-1].fY);
1534 break;
1535 case kQuad_Verb:
1536 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1537 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001538 case kConic_Verb:
1539 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1540 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 case kCubic_Verb:
1542 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1543 pts[-3].fX, pts[-3].fY);
1544 break;
1545 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001546 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 break;
1548 }
reed@google.com277c3f82013-05-31 15:17:50 +00001549 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 }
1551}
1552
reed@google.com63d73742012-01-10 15:33:12 +00001553void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001554 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001555
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001556 const SkPoint* pts = src.fPathRef->pointsEnd();
1557 // we will iterator through src's verbs backwards
1558 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1559 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001560 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001561
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001562 fIsOval = false;
1563
reed@google.com63d73742012-01-10 15:33:12 +00001564 bool needMove = true;
1565 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001566 while (verbs < verbsEnd) {
1567 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001568 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001569
1570 if (needMove) {
1571 --pts;
1572 this->moveTo(pts->fX, pts->fY);
1573 needMove = false;
1574 }
1575 pts -= n;
1576 switch (v) {
1577 case kMove_Verb:
1578 if (needClose) {
1579 this->close();
1580 needClose = false;
1581 }
1582 needMove = true;
1583 pts += 1; // so we see the point in "if (needMove)" above
1584 break;
1585 case kLine_Verb:
1586 this->lineTo(pts[0]);
1587 break;
1588 case kQuad_Verb:
1589 this->quadTo(pts[1], pts[0]);
1590 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001591 case kConic_Verb:
1592 this->conicTo(pts[1], pts[0], *--conicWeights);
1593 break;
reed@google.com63d73742012-01-10 15:33:12 +00001594 case kCubic_Verb:
1595 this->cubicTo(pts[2], pts[1], pts[0]);
1596 break;
1597 case kClose_Verb:
1598 needClose = true;
1599 break;
1600 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001601 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001602 }
1603 }
1604}
1605
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606///////////////////////////////////////////////////////////////////////////////
1607
1608void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1609 SkMatrix matrix;
1610
1611 matrix.setTranslate(dx, dy);
1612 this->transform(matrix, dst);
1613}
1614
1615#include "SkGeometry.h"
1616
1617static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1618 int level = 2) {
1619 if (--level >= 0) {
1620 SkPoint tmp[5];
1621
1622 SkChopQuadAtHalf(pts, tmp);
1623 subdivide_quad_to(path, &tmp[0], level);
1624 subdivide_quad_to(path, &tmp[2], level);
1625 } else {
1626 path->quadTo(pts[1], pts[2]);
1627 }
1628}
1629
1630static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1631 int level = 2) {
1632 if (--level >= 0) {
1633 SkPoint tmp[7];
1634
1635 SkChopCubicAtHalf(pts, tmp);
1636 subdivide_cubic_to(path, &tmp[0], level);
1637 subdivide_cubic_to(path, &tmp[3], level);
1638 } else {
1639 path->cubicTo(pts[1], pts[2], pts[3]);
1640 }
1641}
1642
1643void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1644 SkDEBUGCODE(this->validate();)
1645 if (dst == NULL) {
1646 dst = (SkPath*)this;
1647 }
1648
tomhudson@google.com8d430182011-06-06 19:11:19 +00001649 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 SkPath tmp;
1651 tmp.fFillType = fFillType;
1652
1653 SkPath::Iter iter(*this, false);
1654 SkPoint pts[4];
1655 SkPath::Verb verb;
1656
reed@google.com4a3b7142012-05-16 17:16:46 +00001657 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 switch (verb) {
1659 case kMove_Verb:
1660 tmp.moveTo(pts[0]);
1661 break;
1662 case kLine_Verb:
1663 tmp.lineTo(pts[1]);
1664 break;
1665 case kQuad_Verb:
1666 subdivide_quad_to(&tmp, pts);
1667 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001668 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001669 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001670 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1671 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672 case kCubic_Verb:
1673 subdivide_cubic_to(&tmp, pts);
1674 break;
1675 case kClose_Verb:
1676 tmp.close();
1677 break;
1678 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001679 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001680 break;
1681 }
1682 }
1683
1684 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001685 SkPathRef::Editor ed(&dst->fPathRef);
1686 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001687 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001689 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1690
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001693 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001694 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001696
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001697#ifdef SK_BUILD_FOR_ANDROID
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001698 if (!matrix.isIdentity() && !dst->hasComputedBounds()) {
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001699 GEN_ID_PTR_INC(dst);
1700 }
1701#endif
1702
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001703 if (kUnknown_Direction == fDirection) {
1704 dst->fDirection = kUnknown_Direction;
1705 } else {
1706 SkScalar det2x2 =
1707 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1708 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1709 if (det2x2 < 0) {
1710 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1711 } else if (det2x2 > 0) {
1712 dst->fDirection = fDirection;
1713 } else {
1714 dst->fDirection = kUnknown_Direction;
1715 }
1716 }
1717
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001718 // It's an oval only if it stays a rect.
1719 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001720
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 SkDEBUGCODE(dst->validate();)
1722 }
1723}
1724
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725///////////////////////////////////////////////////////////////////////////////
1726///////////////////////////////////////////////////////////////////////////////
1727
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001729 kEmptyContour_SegmentState, // The current contour is empty. We may be
1730 // starting processing or we may have just
1731 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1733 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1734 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001735};
1736
1737SkPath::Iter::Iter() {
1738#ifdef SK_DEBUG
1739 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001740 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001742 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001743 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744#endif
1745 // need to init enough to make next() harmlessly return kDone_Verb
1746 fVerbs = NULL;
1747 fVerbStop = NULL;
1748 fNeedClose = false;
1749}
1750
1751SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1752 this->setPath(path, forceClose);
1753}
1754
1755void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001756 fPts = path.fPathRef->points();
1757 fVerbs = path.fPathRef->verbs();
1758 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001759 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001760 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001761 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 fForceClose = SkToU8(forceClose);
1763 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001764 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765}
1766
1767bool SkPath::Iter::isClosedContour() const {
1768 if (fVerbs == NULL || fVerbs == fVerbStop) {
1769 return false;
1770 }
1771 if (fForceClose) {
1772 return true;
1773 }
1774
1775 const uint8_t* verbs = fVerbs;
1776 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001777
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 if (kMove_Verb == *(verbs - 1)) {
1779 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 }
1781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 while (verbs > stop) {
1783 // verbs points one beyond the current verb, decrement first.
1784 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 if (kMove_Verb == v) {
1786 break;
1787 }
1788 if (kClose_Verb == v) {
1789 return true;
1790 }
1791 }
1792 return false;
1793}
1794
1795SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001796 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001798 // A special case: if both points are NaN, SkPoint::operation== returns
1799 // false, but the iterator expects that they are treated as the same.
1800 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001801 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1802 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001803 return kClose_Verb;
1804 }
1805
reed@google.com9e25dbf2012-05-15 17:05:38 +00001806 pts[0] = fLastPt;
1807 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808 fLastPt = fMoveTo;
1809 fCloseLine = true;
1810 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001811 } else {
1812 pts[0] = fMoveTo;
1813 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815}
1816
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001818 if (fSegmentState == kAfterMove_SegmentState) {
1819 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1824 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001825 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827}
1828
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829void SkPath::Iter::consumeDegenerateSegments() {
1830 // We need to step over anything that will not move the current draw point
1831 // forward before the next move is seen
1832 const uint8_t* lastMoveVerb = 0;
1833 const SkPoint* lastMovePt = 0;
1834 SkPoint lastPt = fLastPt;
1835 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 switch (verb) {
1838 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 // Keep a record of this most recent move
1840 lastMoveVerb = fVerbs;
1841 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001842 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001843 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 fPts++;
1845 break;
1846
1847 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001848 // A close when we are in a segment is always valid except when it
1849 // follows a move which follows a segment.
1850 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 return;
1852 }
1853 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001854 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 break;
1856
1857 case kLine_Verb:
1858 if (!IsLineDegenerate(lastPt, fPts[0])) {
1859 if (lastMoveVerb) {
1860 fVerbs = lastMoveVerb;
1861 fPts = lastMovePt;
1862 return;
1863 }
1864 return;
1865 }
1866 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fPts++;
1869 break;
1870
reed@google.com277c3f82013-05-31 15:17:50 +00001871 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 case kQuad_Verb:
1873 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1874 if (lastMoveVerb) {
1875 fVerbs = lastMoveVerb;
1876 fPts = lastMovePt;
1877 return;
1878 }
1879 return;
1880 }
1881 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001884 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 break;
1886
1887 case kCubic_Verb:
1888 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1889 if (lastMoveVerb) {
1890 fVerbs = lastMoveVerb;
1891 fPts = lastMovePt;
1892 return;
1893 }
1894 return;
1895 }
1896 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001897 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 fPts += 3;
1899 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001900
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001902 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 }
1904 }
1905}
1906
reed@google.com4a3b7142012-05-16 17:16:46 +00001907SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001908 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 // Close the curve if requested and if there is some curve to close
1912 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 return kLine_Verb;
1915 }
1916 fNeedClose = false;
1917 return kClose_Verb;
1918 }
1919 return kDone_Verb;
1920 }
1921
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 // fVerbs is one beyond the current verb, decrement first
1923 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 const SkPoint* SK_RESTRICT srcPts = fPts;
1925 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926
1927 switch (verb) {
1928 case kMove_Verb:
1929 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 verb = this->autoClose(pts);
1932 if (verb == kClose_Verb) {
1933 fNeedClose = false;
1934 }
1935 return (Verb)verb;
1936 }
1937 if (fVerbs == fVerbStop) { // might be a trailing moveto
1938 return kDone_Verb;
1939 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001940 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001941 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001942 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001943 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 fNeedClose = fForceClose;
1946 break;
1947 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 pts[0] = this->cons_moveTo();
1949 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 fLastPt = srcPts[0];
1951 fCloseLine = false;
1952 srcPts += 1;
1953 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001954 case kConic_Verb:
1955 fConicWeights += 1;
1956 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001958 pts[0] = this->cons_moveTo();
1959 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 fLastPt = srcPts[1];
1961 srcPts += 2;
1962 break;
1963 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001964 pts[0] = this->cons_moveTo();
1965 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 fLastPt = srcPts[2];
1967 srcPts += 3;
1968 break;
1969 case kClose_Verb:
1970 verb = this->autoClose(pts);
1971 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 } else {
1974 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001975 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001978 break;
1979 }
1980 fPts = srcPts;
1981 return (Verb)verb;
1982}
1983
1984///////////////////////////////////////////////////////////////////////////////
1985
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001986SkPath::RawIter::RawIter() {
1987#ifdef SK_DEBUG
1988 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001989 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001990 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1991#endif
1992 // need to init enough to make next() harmlessly return kDone_Verb
1993 fVerbs = NULL;
1994 fVerbStop = NULL;
1995}
1996
1997SkPath::RawIter::RawIter(const SkPath& path) {
1998 this->setPath(path);
1999}
2000
2001void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002002 fPts = path.fPathRef->points();
2003 fVerbs = path.fPathRef->verbs();
2004 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002005 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002006 fMoveTo.fX = fMoveTo.fY = 0;
2007 fLastPt.fX = fLastPt.fY = 0;
2008}
2009
2010SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002011 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002012 if (fVerbs == fVerbStop) {
2013 return kDone_Verb;
2014 }
2015
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002016 // fVerbs points one beyond next verb so decrement first.
2017 unsigned verb = *(--fVerbs);
2018 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019
2020 switch (verb) {
2021 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002022 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002023 fMoveTo = srcPts[0];
2024 fLastPt = fMoveTo;
2025 srcPts += 1;
2026 break;
2027 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002028 pts[0] = fLastPt;
2029 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002030 fLastPt = srcPts[0];
2031 srcPts += 1;
2032 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002033 case kConic_Verb:
2034 fConicWeights += 1;
2035 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002036 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002037 pts[0] = fLastPt;
2038 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002039 fLastPt = srcPts[1];
2040 srcPts += 2;
2041 break;
2042 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002043 pts[0] = fLastPt;
2044 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002045 fLastPt = srcPts[2];
2046 srcPts += 3;
2047 break;
2048 case kClose_Verb:
2049 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002050 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002051 break;
2052 }
2053 fPts = srcPts;
2054 return (Verb)verb;
2055}
2056
2057///////////////////////////////////////////////////////////////////////////////
2058
reed@android.com8a1c16f2008-12-17 15:59:43 +00002059/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002060 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061*/
2062
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002063uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 SkDEBUGCODE(this->validate();)
2065
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002066 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002067 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002068 return SkAlign4(byteCount);
2069 }
2070
2071 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002072
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002073 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002074 (fConvexity << kConvexity_SerializationShift) |
2075 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002076 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002077 (fDirection << kDirection_SerializationShift)
2078#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2079 | (0x1 << kNewFormat_SerializationShift);
2080#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002082 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002083
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002084 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002085
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002086 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002087 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088}
2089
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002090uint32_t SkPath::readFromMemory(const void* storage) {
2091 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002092
reed@google.com98b11f12011-09-21 18:40:27 +00002093 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002094 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2095 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2096 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002097 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002098 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002099#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2100 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2101#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002102
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002103 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2104#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2105 , newFormat, packed)
2106#endif
2107 );
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002108
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002109 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002110
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002111 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002112
2113 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002114 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115}
2116
2117///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118
reed@google.com51bbe752013-01-17 22:07:50 +00002119#include "SkString.h"
2120
2121static void append_scalar(SkString* str, SkScalar value) {
2122 SkString tmp;
2123 tmp.printf("%g", value);
2124 if (tmp.contains('.')) {
2125 tmp.appendUnichar('f');
2126 }
2127 str->append(tmp);
2128}
2129
2130static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002131 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002132 str->append(label);
2133 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002134
reed@google.com51bbe752013-01-17 22:07:50 +00002135 const SkScalar* values = &pts[0].fX;
2136 count *= 2;
2137
2138 for (int i = 0; i < count; ++i) {
2139 append_scalar(str, values[i]);
2140 if (i < count - 1) {
2141 str->append(", ");
2142 }
2143 }
reed@google.com277c3f82013-05-31 15:17:50 +00002144 if (conicWeight >= 0) {
2145 str->append(", ");
2146 append_scalar(str, conicWeight);
2147 }
reed@google.com51bbe752013-01-17 22:07:50 +00002148 str->append(");\n");
2149}
2150
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151void SkPath::dump(bool forceClose, const char title[]) const {
2152 Iter iter(*this, forceClose);
2153 SkPoint pts[4];
2154 Verb verb;
2155
2156 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2157 title ? title : "");
2158
reed@google.com51bbe752013-01-17 22:07:50 +00002159 SkString builder;
2160
reed@google.com4a3b7142012-05-16 17:16:46 +00002161 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162 switch (verb) {
2163 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002164 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002165 break;
2166 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002167 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 break;
2169 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002170 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002172 case kConic_Verb:
2173 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2174 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002176 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177 break;
2178 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002179 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180 break;
2181 default:
2182 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2183 verb = kDone_Verb; // stop the loop
2184 break;
2185 }
2186 }
reed@google.com51bbe752013-01-17 22:07:50 +00002187 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188}
2189
reed@android.come522ca52009-11-23 20:10:41 +00002190void SkPath::dump() const {
2191 this->dump(false);
2192}
2193
2194#ifdef SK_DEBUG
2195void SkPath::validate() const {
2196 SkASSERT(this != NULL);
2197 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002198
djsollen@google.com077348c2012-10-22 20:23:32 +00002199#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002200 if (!fBoundsIsDirty) {
2201 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002202
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002203 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002204 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002205
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002206 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002207 // if we're empty, fBounds may be empty but translated, so we can't
2208 // necessarily compare to bounds directly
2209 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2210 // be [2, 2, 2, 2]
2211 SkASSERT(bounds.isEmpty());
2212 SkASSERT(fBounds.isEmpty());
2213 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002214 if (bounds.isEmpty()) {
2215 SkASSERT(fBounds.isEmpty());
2216 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002217 if (!fBounds.isEmpty()) {
2218 SkASSERT(fBounds.contains(bounds));
2219 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002220 }
reed@android.come522ca52009-11-23 20:10:41 +00002221 }
2222 }
reed@google.com10296cc2011-09-21 12:29:05 +00002223
2224 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002225 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2226 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2227 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002228 case kLine_Verb:
2229 mask |= kLine_SegmentMask;
2230 break;
2231 case kQuad_Verb:
2232 mask |= kQuad_SegmentMask;
2233 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002234 case kConic_Verb:
2235 mask |= kConic_SegmentMask;
2236 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002237 case kCubic_Verb:
2238 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002239 case kMove_Verb: // these verbs aren't included in the segment mask.
2240 case kClose_Verb:
2241 break;
2242 case kDone_Verb:
2243 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2244 break;
2245 default:
2246 SkDEBUGFAIL("Unknown Verb");
2247 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002248 }
2249 }
2250 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002251#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002252}
djsollen@google.com077348c2012-10-22 20:23:32 +00002253#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002254
reed@google.com04863fa2011-05-15 04:08:24 +00002255///////////////////////////////////////////////////////////////////////////////
2256
reed@google.com0b7b9822011-05-16 12:29:27 +00002257static int sign(SkScalar x) { return x < 0; }
2258#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002259
reed@google.com04863fa2011-05-15 04:08:24 +00002260static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002261 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002262}
2263
2264// only valid for a single contour
2265struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002266 Convexicator()
2267 : fPtCount(0)
2268 , fConvexity(SkPath::kConvex_Convexity)
2269 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002270 fSign = 0;
2271 // warnings
2272 fCurrPt.set(0, 0);
2273 fVec0.set(0, 0);
2274 fVec1.set(0, 0);
2275 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002276
2277 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002278 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002279 }
2280
2281 SkPath::Convexity getConvexity() const { return fConvexity; }
2282
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002283 /** The direction returned is only valid if the path is determined convex */
2284 SkPath::Direction getDirection() const { return fDirection; }
2285
reed@google.com04863fa2011-05-15 04:08:24 +00002286 void addPt(const SkPoint& pt) {
2287 if (SkPath::kConcave_Convexity == fConvexity) {
2288 return;
2289 }
2290
2291 if (0 == fPtCount) {
2292 fCurrPt = pt;
2293 ++fPtCount;
2294 } else {
2295 SkVector vec = pt - fCurrPt;
2296 if (vec.fX || vec.fY) {
2297 fCurrPt = pt;
2298 if (++fPtCount == 2) {
2299 fFirstVec = fVec1 = vec;
2300 } else {
2301 SkASSERT(fPtCount > 2);
2302 this->addVec(vec);
2303 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002304
reed@google.com85b6e392011-05-15 20:25:17 +00002305 int sx = sign(vec.fX);
2306 int sy = sign(vec.fY);
2307 fDx += (sx != fSx);
2308 fDy += (sy != fSy);
2309 fSx = sx;
2310 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002311
reed@google.com85b6e392011-05-15 20:25:17 +00002312 if (fDx > 3 || fDy > 3) {
2313 fConvexity = SkPath::kConcave_Convexity;
2314 }
reed@google.com04863fa2011-05-15 04:08:24 +00002315 }
2316 }
2317 }
2318
2319 void close() {
2320 if (fPtCount > 2) {
2321 this->addVec(fFirstVec);
2322 }
2323 }
2324
2325private:
2326 void addVec(const SkVector& vec) {
2327 SkASSERT(vec.fX || vec.fY);
2328 fVec0 = fVec1;
2329 fVec1 = vec;
2330 int sign = CrossProductSign(fVec0, fVec1);
2331 if (0 == fSign) {
2332 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002333 if (1 == sign) {
2334 fDirection = SkPath::kCW_Direction;
2335 } else if (-1 == sign) {
2336 fDirection = SkPath::kCCW_Direction;
2337 }
reed@google.com04863fa2011-05-15 04:08:24 +00002338 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002339 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002340 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002341 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002342 }
2343 }
2344 }
2345
2346 SkPoint fCurrPt;
2347 SkVector fVec0, fVec1, fFirstVec;
2348 int fPtCount; // non-degenerate points
2349 int fSign;
2350 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002351 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002352 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002353};
2354
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002355SkPath::Convexity SkPath::internalGetConvexity() const {
2356 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002357 SkPoint pts[4];
2358 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002359 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002360
2361 int contourCount = 0;
2362 int count;
2363 Convexicator state;
2364
2365 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2366 switch (verb) {
2367 case kMove_Verb:
2368 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002369 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002370 return kConcave_Convexity;
2371 }
2372 pts[1] = pts[0];
2373 count = 1;
2374 break;
2375 case kLine_Verb: count = 1; break;
2376 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002377 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002378 case kCubic_Verb: count = 3; break;
2379 case kClose_Verb:
2380 state.close();
2381 count = 0;
2382 break;
2383 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002384 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002385 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002386 return kConcave_Convexity;
2387 }
2388
2389 for (int i = 1; i <= count; i++) {
2390 state.addPt(pts[i]);
2391 }
2392 // early exit
2393 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002394 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002395 return kConcave_Convexity;
2396 }
2397 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002398 fConvexity = state.getConvexity();
2399 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2400 fDirection = state.getDirection();
2401 }
2402 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002403}
reed@google.com69a99432012-01-10 18:00:10 +00002404
2405///////////////////////////////////////////////////////////////////////////////
2406
2407class ContourIter {
2408public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002409 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002410
2411 bool done() const { return fDone; }
2412 // if !done() then these may be called
2413 int count() const { return fCurrPtCount; }
2414 const SkPoint* pts() const { return fCurrPt; }
2415 void next();
2416
2417private:
2418 int fCurrPtCount;
2419 const SkPoint* fCurrPt;
2420 const uint8_t* fCurrVerb;
2421 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002422 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002423 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002424 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002425};
2426
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002427ContourIter::ContourIter(const SkPathRef& pathRef) {
2428 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002429 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002430 fCurrPt = pathRef.points();
2431 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002432 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002433 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002434 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002435 this->next();
2436}
2437
2438void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002439 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002440 fDone = true;
2441 }
2442 if (fDone) {
2443 return;
2444 }
2445
2446 // skip pts of prev contour
2447 fCurrPt += fCurrPtCount;
2448
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002449 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002450 int ptCount = 1; // moveTo
2451 const uint8_t* verbs = fCurrVerb;
2452
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002453 for (--verbs; verbs > fStopVerbs; --verbs) {
2454 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002455 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002456 goto CONTOUR_END;
2457 case SkPath::kLine_Verb:
2458 ptCount += 1;
2459 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002460 case SkPath::kConic_Verb:
2461 fCurrConicWeight += 1;
2462 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002463 case SkPath::kQuad_Verb:
2464 ptCount += 2;
2465 break;
2466 case SkPath::kCubic_Verb:
2467 ptCount += 3;
2468 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002469 case SkPath::kClose_Verb:
2470 break;
2471 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002472 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002473 break;
2474 }
2475 }
2476CONTOUR_END:
2477 fCurrPtCount = ptCount;
2478 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002479 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002480}
2481
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002482// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002483static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002484 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2485 // We may get 0 when the above subtracts underflow. We expect this to be
2486 // very rare and lazily promote to double.
2487 if (0 == cross) {
2488 double p0x = SkScalarToDouble(p0.fX);
2489 double p0y = SkScalarToDouble(p0.fY);
2490
2491 double p1x = SkScalarToDouble(p1.fX);
2492 double p1y = SkScalarToDouble(p1.fY);
2493
2494 double p2x = SkScalarToDouble(p2.fX);
2495 double p2y = SkScalarToDouble(p2.fY);
2496
2497 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2498 (p1y - p0y) * (p2x - p0x));
2499
2500 }
2501 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002502}
2503
reed@google.comc1ea60a2012-01-31 15:15:36 +00002504// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002505static int find_max_y(const SkPoint pts[], int count) {
2506 SkASSERT(count > 0);
2507 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002508 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002509 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002510 SkScalar y = pts[i].fY;
2511 if (y > max) {
2512 max = y;
2513 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002514 }
2515 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002516 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002517}
2518
reed@google.comcabaf1d2012-01-11 21:03:05 +00002519static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2520 int i = index;
2521 for (;;) {
2522 i = (i + inc) % n;
2523 if (i == index) { // we wrapped around, so abort
2524 break;
2525 }
2526 if (pts[index] != pts[i]) { // found a different point, success!
2527 break;
2528 }
2529 }
2530 return i;
2531}
2532
reed@google.comc1ea60a2012-01-31 15:15:36 +00002533/**
2534 * Starting at index, and moving forward (incrementing), find the xmin and
2535 * xmax of the contiguous points that have the same Y.
2536 */
2537static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2538 int* maxIndexPtr) {
2539 const SkScalar y = pts[index].fY;
2540 SkScalar min = pts[index].fX;
2541 SkScalar max = min;
2542 int minIndex = index;
2543 int maxIndex = index;
2544 for (int i = index + 1; i < n; ++i) {
2545 if (pts[i].fY != y) {
2546 break;
2547 }
2548 SkScalar x = pts[i].fX;
2549 if (x < min) {
2550 min = x;
2551 minIndex = i;
2552 } else if (x > max) {
2553 max = x;
2554 maxIndex = i;
2555 }
2556 }
2557 *maxIndexPtr = maxIndex;
2558 return minIndex;
2559}
2560
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002561static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002562 if (dir) {
2563 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2564 }
reed@google.comac8543f2012-01-30 20:51:25 +00002565}
2566
reed@google.comc1ea60a2012-01-31 15:15:36 +00002567#if 0
2568#include "SkString.h"
2569#include "../utils/SkParsePath.h"
2570static void dumpPath(const SkPath& path) {
2571 SkString str;
2572 SkParsePath::ToSVGString(path, &str);
2573 SkDebugf("%s\n", str.c_str());
2574}
2575#endif
2576
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002577namespace {
2578// for use with convex_dir_test
2579double mul(double a, double b) { return a * b; }
2580SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2581double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2582SkScalar toScalar(SkScalar a) { return a; }
2583
2584// determines the winding direction of a convex polygon with the precision
2585// of T. CAST_SCALAR casts an SkScalar to T.
2586template <typename T, T (CAST_SCALAR)(SkScalar)>
2587bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2588 // we find the first three points that form a non-degenerate
2589 // triangle. If there are no such points then the path is
2590 // degenerate. The first is always point 0. Now we find the second
2591 // point.
2592 int i = 0;
2593 enum { kX = 0, kY = 1 };
2594 T v0[2];
2595 while (1) {
2596 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2597 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2598 if (v0[kX] || v0[kY]) {
2599 break;
2600 }
2601 if (++i == n - 1) {
2602 return false;
2603 }
2604 }
2605 // now find a third point that is not colinear with the first two
2606 // points and check the orientation of the triangle (which will be
2607 // the same as the orientation of the path).
2608 for (++i; i < n; ++i) {
2609 T v1[2];
2610 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2611 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2612 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2613 if (0 != cross) {
2614 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2615 return true;
2616 }
2617 }
2618 return false;
2619}
2620}
2621
reed@google.comac8543f2012-01-30 20:51:25 +00002622/*
2623 * We loop through all contours, and keep the computed cross-product of the
2624 * contour that contained the global y-max. If we just look at the first
2625 * contour, we may find one that is wound the opposite way (correctly) since
2626 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2627 * that is outer most (or at least has the global y-max) before we can consider
2628 * its cross product.
2629 */
reed@google.com69a99432012-01-10 18:00:10 +00002630bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002631// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002632 // don't want to pay the cost for computing this if it
2633 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002634
2635 if (kUnknown_Direction != fDirection) {
2636 *dir = static_cast<Direction>(fDirection);
2637 return true;
2638 }
reed@google.com69a99432012-01-10 18:00:10 +00002639 const Convexity conv = this->getConvexityOrUnknown();
2640
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002641 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002642
reed@google.comac8543f2012-01-30 20:51:25 +00002643 // initialize with our logical y-min
2644 SkScalar ymax = this->getBounds().fTop;
2645 SkScalar ymaxCross = 0;
2646
reed@google.com69a99432012-01-10 18:00:10 +00002647 for (; !iter.done(); iter.next()) {
2648 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002649 if (n < 3) {
2650 continue;
2651 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002652
reed@google.comcabaf1d2012-01-11 21:03:05 +00002653 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002654 SkScalar cross = 0;
2655 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002656 // We try first at scalar precision, and then again at double
2657 // precision. This is because the vectors computed between distant
2658 // points may lose too much precision.
2659 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002660 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002661 return true;
2662 }
2663 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002664 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002665 return true;
2666 } else {
2667 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002668 }
2669 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002670 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002671 if (pts[index].fY < ymax) {
2672 continue;
2673 }
2674
reed@google.comc1ea60a2012-01-31 15:15:36 +00002675 // If there is more than 1 distinct point at the y-max, we take the
2676 // x-min and x-max of them and just subtract to compute the dir.
2677 if (pts[(index + 1) % n].fY == pts[index].fY) {
2678 int maxIndex;
2679 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002680 if (minIndex == maxIndex) {
2681 goto TRY_CROSSPROD;
2682 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002683 SkASSERT(pts[minIndex].fY == pts[index].fY);
2684 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2685 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2686 // we just subtract the indices, and let that auto-convert to
2687 // SkScalar, since we just want - or + to signal the direction.
2688 cross = minIndex - maxIndex;
2689 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002690 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002691 // Find a next and prev index to use for the cross-product test,
2692 // but we try to find pts that form non-zero vectors from pts[index]
2693 //
2694 // Its possible that we can't find two non-degenerate vectors, so
2695 // we have to guard our search (e.g. all the pts could be in the
2696 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002697
reed@google.comc1ea60a2012-01-31 15:15:36 +00002698 // we pass n - 1 instead of -1 so we don't foul up % operator by
2699 // passing it a negative LH argument.
2700 int prev = find_diff_pt(pts, index, n, n - 1);
2701 if (prev == index) {
2702 // completely degenerate, skip to next contour
2703 continue;
2704 }
2705 int next = find_diff_pt(pts, index, n, 1);
2706 SkASSERT(next != index);
2707 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002708 // if we get a zero and the points are horizontal, then we look at the spread in
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002709 // x-direction. We really should continue to walk away from the degeneracy until
2710 // there is a divergence.
2711 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002712 // construct the subtract so we get the correct Direction below
2713 cross = pts[index].fX - pts[next].fX;
2714 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002715 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002716
reed@google.comac8543f2012-01-30 20:51:25 +00002717 if (cross) {
2718 // record our best guess so far
2719 ymax = pts[index].fY;
2720 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002721 }
reed@google.com69a99432012-01-10 18:00:10 +00002722 }
2723 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002724 if (ymaxCross) {
2725 crossToDir(ymaxCross, dir);
2726 fDirection = *dir;
2727 return true;
2728 } else {
2729 return false;
2730 }
reed@google.comac8543f2012-01-30 20:51:25 +00002731}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002732
2733///////////////////////////////////////////////////////////////////////////////
2734
2735static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2736 SkScalar D, SkScalar t) {
2737 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2738}
2739
2740static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2741 SkScalar t) {
2742 SkScalar A = c3 + 3*(c1 - c2) - c0;
2743 SkScalar B = 3*(c2 - c1 - c1 + c0);
2744 SkScalar C = 3*(c1 - c0);
2745 SkScalar D = c0;
2746 return eval_cubic_coeff(A, B, C, D, t);
2747}
2748
2749/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2750 t value such that cubic(t) = target
2751 */
2752static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2753 SkScalar target, SkScalar* t) {
2754 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2755 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002756
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002757 SkScalar D = c0 - target;
2758 SkScalar A = c3 + 3*(c1 - c2) - c0;
2759 SkScalar B = 3*(c2 - c1 - c1 + c0);
2760 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002761
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2763 SkScalar minT = 0;
2764 SkScalar maxT = SK_Scalar1;
2765 SkScalar mid;
2766 int i;
2767 for (i = 0; i < 16; i++) {
2768 mid = SkScalarAve(minT, maxT);
2769 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2770 if (delta < 0) {
2771 minT = mid;
2772 delta = -delta;
2773 } else {
2774 maxT = mid;
2775 }
2776 if (delta < TOLERANCE) {
2777 break;
2778 }
2779 }
2780 *t = mid;
2781 return true;
2782}
2783
2784template <size_t N> static void find_minmax(const SkPoint pts[],
2785 SkScalar* minPtr, SkScalar* maxPtr) {
2786 SkScalar min, max;
2787 min = max = pts[0].fX;
2788 for (size_t i = 1; i < N; ++i) {
2789 min = SkMinScalar(min, pts[i].fX);
2790 max = SkMaxScalar(max, pts[i].fX);
2791 }
2792 *minPtr = min;
2793 *maxPtr = max;
2794}
2795
2796static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2797 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002798
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 int dir = 1;
2800 if (pts[0].fY > pts[3].fY) {
2801 storage[0] = pts[3];
2802 storage[1] = pts[2];
2803 storage[2] = pts[1];
2804 storage[3] = pts[0];
2805 pts = storage;
2806 dir = -1;
2807 }
2808 if (y < pts[0].fY || y >= pts[3].fY) {
2809 return 0;
2810 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002811
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002812 // quickreject or quickaccept
2813 SkScalar min, max;
2814 find_minmax<4>(pts, &min, &max);
2815 if (x < min) {
2816 return 0;
2817 }
2818 if (x > max) {
2819 return dir;
2820 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 // compute the actual x(t) value
2823 SkScalar t, xt;
2824 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2825 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2826 } else {
2827 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2828 xt = y < mid ? pts[0].fX : pts[3].fX;
2829 }
2830 return xt < x ? dir : 0;
2831}
2832
2833static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2834 SkPoint dst[10];
2835 int n = SkChopCubicAtYExtrema(pts, dst);
2836 int w = 0;
2837 for (int i = 0; i <= n; ++i) {
2838 w += winding_mono_cubic(&dst[i * 3], x, y);
2839 }
2840 return w;
2841}
2842
2843static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2844 SkScalar y0 = pts[0].fY;
2845 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002846
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002847 int dir = 1;
2848 if (y0 > y2) {
2849 SkTSwap(y0, y2);
2850 dir = -1;
2851 }
2852 if (y < y0 || y >= y2) {
2853 return 0;
2854 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002855
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002856 // bounds check on X (not required. is it faster?)
2857#if 0
2858 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2859 return 0;
2860 }
2861#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 SkScalar roots[2];
2864 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2865 2 * (pts[1].fY - pts[0].fY),
2866 pts[0].fY - y,
2867 roots);
2868 SkASSERT(n <= 1);
2869 SkScalar xt;
2870 if (0 == n) {
2871 SkScalar mid = SkScalarAve(y0, y2);
2872 // Need [0] and [2] if dir == 1
2873 // and [2] and [0] if dir == -1
2874 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2875 } else {
2876 SkScalar t = roots[0];
2877 SkScalar C = pts[0].fX;
2878 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2879 SkScalar B = 2 * (pts[1].fX - C);
2880 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2881 }
2882 return xt < x ? dir : 0;
2883}
2884
2885static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2886 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2887 if (y0 == y1) {
2888 return true;
2889 }
2890 if (y0 < y1) {
2891 return y1 <= y2;
2892 } else {
2893 return y1 >= y2;
2894 }
2895}
2896
2897static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2898 SkPoint dst[5];
2899 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002900
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002901 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2902 n = SkChopQuadAtYExtrema(pts, dst);
2903 pts = dst;
2904 }
2905 int w = winding_mono_quad(pts, x, y);
2906 if (n > 0) {
2907 w += winding_mono_quad(&pts[2], x, y);
2908 }
2909 return w;
2910}
2911
2912static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2913 SkScalar x0 = pts[0].fX;
2914 SkScalar y0 = pts[0].fY;
2915 SkScalar x1 = pts[1].fX;
2916 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002917
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002918 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002919
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 int dir = 1;
2921 if (y0 > y1) {
2922 SkTSwap(y0, y1);
2923 dir = -1;
2924 }
2925 if (y < y0 || y >= y1) {
2926 return 0;
2927 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002928
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2930 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002931
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002932 if (SkScalarSignAsInt(cross) == dir) {
2933 dir = 0;
2934 }
2935 return dir;
2936}
2937
2938bool SkPath::contains(SkScalar x, SkScalar y) const {
2939 bool isInverse = this->isInverseFillType();
2940 if (this->isEmpty()) {
2941 return isInverse;
2942 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002943
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002944 const SkRect& bounds = this->getBounds();
2945 if (!bounds.contains(x, y)) {
2946 return isInverse;
2947 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002948
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002949 SkPath::Iter iter(*this, true);
2950 bool done = false;
2951 int w = 0;
2952 do {
2953 SkPoint pts[4];
2954 switch (iter.next(pts, false)) {
2955 case SkPath::kMove_Verb:
2956 case SkPath::kClose_Verb:
2957 break;
2958 case SkPath::kLine_Verb:
2959 w += winding_line(pts, x, y);
2960 break;
2961 case SkPath::kQuad_Verb:
2962 w += winding_quad(pts, x, y);
2963 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002964 case SkPath::kConic_Verb:
2965 SkASSERT(0);
2966 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002967 case SkPath::kCubic_Verb:
2968 w += winding_cubic(pts, x, y);
2969 break;
2970 case SkPath::kDone_Verb:
2971 done = true;
2972 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002973 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002974 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002975
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 switch (this->getFillType()) {
2977 case SkPath::kEvenOdd_FillType:
2978 case SkPath::kInverseEvenOdd_FillType:
2979 w &= 1;
2980 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002981 default:
2982 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 }
2984 return SkToBool(w);
2985}