blob: f9a17df48d4219834f0eecc51f3ca94b23eb1857 [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
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902static void add_corner_arc(SkPath* path, const SkRect& rect,
903 SkScalar rx, SkScalar ry, int startAngle,
904 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000905 // These two asserts are not sufficient, since really we want to know
906 // that the pair of radii (e.g. left and right, or top and bottom) sum
907 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000908 // these conservative asserts.
909 SkASSERT(0 <= rx && rx <= rect.width());
910 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000911
reed@android.com8a1c16f2008-12-17 15:59:43 +0000912 SkRect r;
913 r.set(-rx, -ry, rx, ry);
914
915 switch (startAngle) {
916 case 0:
917 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
918 break;
919 case 90:
920 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
921 break;
922 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
923 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000924 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925 }
reed@google.comabf15c12011-01-18 20:35:51 +0000926
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927 SkScalar start = SkIntToScalar(startAngle);
928 SkScalar sweep = SkIntToScalar(90);
929 if (SkPath::kCCW_Direction == dir) {
930 start += sweep;
931 sweep = -sweep;
932 }
reed@google.comabf15c12011-01-18 20:35:51 +0000933
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934 path->arcTo(r, start, sweep, forceMoveTo);
935}
936
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000937void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000938 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000939 SkRRect rrect;
940 rrect.setRectRadii(rect, (const SkVector*) radii);
941 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942}
943
reed@google.com4ed0fb72012-12-12 20:48:18 +0000944void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000945 assert_known_direction(dir);
946
947 if (rrect.isEmpty()) {
948 return;
949 }
950
reed@google.com4ed0fb72012-12-12 20:48:18 +0000951 const SkRect& bounds = rrect.getBounds();
952
953 if (rrect.isRect()) {
954 this->addRect(bounds, dir);
955 } else if (rrect.isOval()) {
956 this->addOval(bounds, dir);
957 } else if (rrect.isSimple()) {
958 const SkVector& rad = rrect.getSimpleRadii();
959 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
960 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000961 SkAutoPathBoundsUpdate apbu(this, bounds);
962
963 if (kCW_Direction == dir) {
964 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
965 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
966 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
967 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
968 } else {
969 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
970 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
971 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
972 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
973 }
974 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +0000975 }
976}
977
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000978bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000979 int count = fPathRef->countVerbs();
980 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
981 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000982 if (*verbs == kLine_Verb ||
983 *verbs == kQuad_Verb ||
984 *verbs == kCubic_Verb) {
985 return false;
986 }
987 ++verbs;
988 }
989 return true;
990}
991
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +0000992#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
993
994void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
995 Direction dir) {
996 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +0000997
humper@google.com75e3ca12013-04-08 21:44:11 +0000998 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +0000999 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001000 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001001 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001002 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1003 return;
1004 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001005
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001006 SkScalar w = rect.width();
1007 SkScalar halfW = SkScalarHalf(w);
1008 SkScalar h = rect.height();
1009 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001010
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001011 if (halfW <= 0 || halfH <= 0) {
1012 return;
1013 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001014
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001015 bool skip_hori = rx >= halfW;
1016 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001017
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001018 if (skip_hori && skip_vert) {
1019 this->addOval(rect, dir);
1020 return;
1021 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001022
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001023 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001024
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001025 SkAutoPathBoundsUpdate apbu(this, rect);
1026 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001027
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001028 if (skip_hori) {
1029 rx = halfW;
1030 } else if (skip_vert) {
1031 ry = halfH;
1032 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001033
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001034 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1035 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001036
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001037 this->incReserve(17);
1038 this->moveTo(rect.fRight - rx, rect.fTop);
1039 if (dir == kCCW_Direction) {
1040 if (!skip_hori) {
1041 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1042 }
1043 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1044 rect.fLeft, rect.fTop + ry - sy,
1045 rect.fLeft, rect.fTop + ry); // top-left
1046 if (!skip_vert) {
1047 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1048 }
1049 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1050 rect.fLeft + rx - sx, rect.fBottom,
1051 rect.fLeft + rx, rect.fBottom); // bot-left
1052 if (!skip_hori) {
1053 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1054 }
1055 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1056 rect.fRight, rect.fBottom - ry + sy,
1057 rect.fRight, rect.fBottom - ry); // bot-right
1058 if (!skip_vert) {
1059 this->lineTo(rect.fRight, rect.fTop + ry);
1060 }
1061 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1062 rect.fRight - rx + sx, rect.fTop,
1063 rect.fRight - rx, rect.fTop); // top-right
1064 } else {
1065 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1066 rect.fRight, rect.fTop + ry - sy,
1067 rect.fRight, rect.fTop + ry); // top-right
1068 if (!skip_vert) {
1069 this->lineTo(rect.fRight, rect.fBottom - ry);
1070 }
1071 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1072 rect.fRight - rx + sx, rect.fBottom,
1073 rect.fRight - rx, rect.fBottom); // bot-right
1074 if (!skip_hori) {
1075 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1076 }
1077 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1078 rect.fLeft, rect.fBottom - ry + sy,
1079 rect.fLeft, rect.fBottom - ry); // bot-left
1080 if (!skip_vert) {
1081 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1082 }
1083 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1084 rect.fLeft + rx - sx, rect.fTop,
1085 rect.fLeft + rx, rect.fTop); // top-left
1086 if (!skip_hori) {
1087 this->lineTo(rect.fRight - rx, rect.fTop); // top
1088 }
1089 }
1090 this->close();
1091}
1092
reed@android.com8a1c16f2008-12-17 15:59:43 +00001093void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001094 assert_known_direction(dir);
1095
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001096 /* If addOval() is called after previous moveTo(),
1097 this path is still marked as an oval. This is used to
1098 fit into WebKit's calling sequences.
1099 We can't simply check isEmpty() in this case, as additional
1100 moveTo() would mark the path non empty.
1101 */
1102 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001103 if (fIsOval) {
1104 fDirection = dir;
1105 } else {
1106 fDirection = kUnknown_Direction;
1107 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001108
1109 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001110 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001111
reed@android.com8a1c16f2008-12-17 15:59:43 +00001112 SkAutoPathBoundsUpdate apbu(this, oval);
1113
1114 SkScalar cx = oval.centerX();
1115 SkScalar cy = oval.centerY();
1116 SkScalar rx = SkScalarHalf(oval.width());
1117 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1120 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1121 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1122 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1123
1124 /*
1125 To handle imprecision in computing the center and radii, we revert to
1126 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1127 to ensure that we don't exceed the oval's bounds *ever*, since we want
1128 to use oval for our fast-bounds, rather than have to recompute it.
1129 */
1130 const SkScalar L = oval.fLeft; // cx - rx
1131 const SkScalar T = oval.fTop; // cy - ry
1132 const SkScalar R = oval.fRight; // cx + rx
1133 const SkScalar B = oval.fBottom; // cy + ry
1134
1135 this->incReserve(17); // 8 quads + close
1136 this->moveTo(R, cy);
1137 if (dir == kCCW_Direction) {
1138 this->quadTo( R, cy - sy, cx + mx, cy - my);
1139 this->quadTo(cx + sx, T, cx , T);
1140 this->quadTo(cx - sx, T, cx - mx, cy - my);
1141 this->quadTo( L, cy - sy, L, cy );
1142 this->quadTo( L, cy + sy, cx - mx, cy + my);
1143 this->quadTo(cx - sx, B, cx , B);
1144 this->quadTo(cx + sx, B, cx + mx, cy + my);
1145 this->quadTo( R, cy + sy, R, cy );
1146 } else {
1147 this->quadTo( R, cy + sy, cx + mx, cy + my);
1148 this->quadTo(cx + sx, B, cx , B);
1149 this->quadTo(cx - sx, B, cx - mx, cy + my);
1150 this->quadTo( L, cy + sy, L, cy );
1151 this->quadTo( L, cy - sy, cx - mx, cy - my);
1152 this->quadTo(cx - sx, T, cx , T);
1153 this->quadTo(cx + sx, T, cx + mx, cy - my);
1154 this->quadTo( R, cy - sy, R, cy );
1155 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001156 this->close();
1157}
1158
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001159bool SkPath::isOval(SkRect* rect) const {
1160 if (fIsOval && rect) {
1161 *rect = getBounds();
1162 }
1163
1164 return fIsOval;
1165}
1166
reed@android.com8a1c16f2008-12-17 15:59:43 +00001167void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1168 if (r > 0) {
1169 SkRect rect;
1170 rect.set(x - r, y - r, x + r, y + r);
1171 this->addOval(rect, dir);
1172 }
1173}
1174
1175#include "SkGeometry.h"
1176
1177static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1178 SkScalar sweepAngle,
1179 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001180
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001181 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001182 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1183 // Chrome uses this path to move into and out of ovals. If not
1184 // treated as a special case the moves can distort the oval's
1185 // bounding box (and break the circle special case).
1186 pts[0].set(oval.fRight, oval.centerY());
1187 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001188 } else if (0 == oval.width() && 0 == oval.height()) {
1189 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001190 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001191 // a rect.
1192 // TODO: optimizing the case where only one of width or height is zero
1193 // should also be considered. This case, however, doesn't seem to be
1194 // as common as the single point case.
1195 pts[0].set(oval.fRight, oval.fTop);
1196 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001197 }
1198
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199 SkVector start, stop;
1200
1201 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1202 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1203 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001204
1205 /* If the sweep angle is nearly (but less than) 360, then due to precision
1206 loss in radians-conversion and/or sin/cos, we may end up with coincident
1207 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1208 of drawing a nearly complete circle (good).
1209 e.g. canvas.drawArc(0, 359.99, ...)
1210 -vs- canvas.drawArc(0, 359.9, ...)
1211 We try to detect this edge case, and tweak the stop vector
1212 */
1213 if (start == stop) {
1214 SkScalar sw = SkScalarAbs(sweepAngle);
1215 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1216 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1217 // make a guess at a tiny angle (in radians) to tweak by
1218 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1219 // not sure how much will be enough, so we use a loop
1220 do {
1221 stopRad -= deltaRad;
1222 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1223 } while (start == stop);
1224 }
1225 }
1226
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1230 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 return SkBuildQuadArc(start, stop,
1233 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1234 &matrix, pts);
1235}
1236
1237void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1238 bool forceMoveTo) {
1239 if (oval.width() < 0 || oval.height() < 0) {
1240 return;
1241 }
1242
1243 SkPoint pts[kSkBuildQuadArcStorage];
1244 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1245 SkASSERT((count & 1) == 1);
1246
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001247 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 forceMoveTo = true;
1249 }
1250 this->incReserve(count);
1251 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1252 for (int i = 1; i < count; i += 2) {
1253 this->quadTo(pts[i], pts[i+1]);
1254 }
1255}
1256
1257void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1258 SkScalar sweepAngle) {
1259 if (oval.isEmpty() || 0 == sweepAngle) {
1260 return;
1261 }
1262
1263 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1264
1265 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1266 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1267 return;
1268 }
1269
1270 SkPoint pts[kSkBuildQuadArcStorage];
1271 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1272
1273 this->incReserve(count);
1274 this->moveTo(pts[0]);
1275 for (int i = 1; i < count; i += 2) {
1276 this->quadTo(pts[i], pts[i+1]);
1277 }
1278}
1279
1280/*
1281 Need to handle the case when the angle is sharp, and our computed end-points
1282 for the arc go behind pt1 and/or p2...
1283*/
1284void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1285 SkScalar radius) {
1286 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001287
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 // need to know our prev pt so we can construct tangent vectors
1289 {
1290 SkPoint start;
1291 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001292 // Handle degenerate cases by adding a line to the first point and
1293 // bailing out.
1294 if ((x1 == start.fX && y1 == start.fY) ||
1295 (x1 == x2 && y1 == y2) ||
1296 radius == 0) {
1297 this->lineTo(x1, y1);
1298 return;
1299 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 before.setNormalize(x1 - start.fX, y1 - start.fY);
1301 after.setNormalize(x2 - x1, y2 - y1);
1302 }
reed@google.comabf15c12011-01-18 20:35:51 +00001303
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 SkScalar cosh = SkPoint::DotProduct(before, after);
1305 SkScalar sinh = SkPoint::CrossProduct(before, after);
1306
1307 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001308 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 return;
1310 }
reed@google.comabf15c12011-01-18 20:35:51 +00001311
reed@android.com8a1c16f2008-12-17 15:59:43 +00001312 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1313 if (dist < 0) {
1314 dist = -dist;
1315 }
1316
1317 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1318 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1319 SkRotationDirection arcDir;
1320
1321 // now turn before/after into normals
1322 if (sinh > 0) {
1323 before.rotateCCW();
1324 after.rotateCCW();
1325 arcDir = kCW_SkRotationDirection;
1326 } else {
1327 before.rotateCW();
1328 after.rotateCW();
1329 arcDir = kCCW_SkRotationDirection;
1330 }
1331
1332 SkMatrix matrix;
1333 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001334
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335 matrix.setScale(radius, radius);
1336 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1337 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001338
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001340
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 this->incReserve(count);
1342 // [xx,yy] == pts[0]
1343 this->lineTo(xx, yy);
1344 for (int i = 1; i < count; i += 2) {
1345 this->quadTo(pts[i], pts[i+1]);
1346 }
1347}
1348
1349///////////////////////////////////////////////////////////////////////////////
1350
1351void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1352 SkMatrix matrix;
1353
1354 matrix.setTranslate(dx, dy);
1355 this->addPath(path, matrix);
1356}
1357
1358void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001359 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001361 fIsOval = false;
1362
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001363 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 SkPoint pts[4];
1365 Verb verb;
1366
1367 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1368
1369 while ((verb = iter.next(pts)) != kDone_Verb) {
1370 switch (verb) {
1371 case kMove_Verb:
1372 proc(matrix, &pts[0], &pts[0], 1);
1373 this->moveTo(pts[0]);
1374 break;
1375 case kLine_Verb:
1376 proc(matrix, &pts[1], &pts[1], 1);
1377 this->lineTo(pts[1]);
1378 break;
1379 case kQuad_Verb:
1380 proc(matrix, &pts[1], &pts[1], 2);
1381 this->quadTo(pts[1], pts[2]);
1382 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001383 case kConic_Verb:
1384 proc(matrix, &pts[1], &pts[1], 2);
1385 this->conicTo(pts[1], pts[2], iter.conicWeight());
1386 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001387 case kCubic_Verb:
1388 proc(matrix, &pts[1], &pts[1], 3);
1389 this->cubicTo(pts[1], pts[2], pts[3]);
1390 break;
1391 case kClose_Verb:
1392 this->close();
1393 break;
1394 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001395 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 }
1397 }
1398}
1399
1400///////////////////////////////////////////////////////////////////////////////
1401
reed@google.com277c3f82013-05-31 15:17:50 +00001402static int pts_in_verb(unsigned verb) {
1403 static const uint8_t gPtsInVerb[] = {
1404 1, // kMove
1405 1, // kLine
1406 2, // kQuad
1407 2, // kConic
1408 3, // kCubic
1409 0, // kClose
1410 0 // kDone
1411 };
1412
1413 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1414 return gPtsInVerb[verb];
1415}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416
1417// ignore the initial moveto, and stop when the 1st contour ends
1418void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001419 int i, vcount = path.fPathRef->countVerbs();
1420 // exit early if the path is empty, or just has a moveTo.
1421 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 return;
1423 }
1424
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001425 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001427 fIsOval = false;
1428
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001429 const uint8_t* verbs = path.fPathRef->verbs();
1430 // skip the initial moveTo
1431 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001432 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001434 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001436 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 case kLine_Verb:
1438 this->lineTo(pts[0].fX, pts[0].fY);
1439 break;
1440 case kQuad_Verb:
1441 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1442 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001443 case kConic_Verb:
1444 this->conicTo(pts[0], pts[1], *conicWeight++);
1445 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001447 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 +00001448 break;
1449 case kClose_Verb:
1450 return;
1451 }
reed@google.com277c3f82013-05-31 15:17:50 +00001452 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 }
1454}
1455
1456// ignore the last point of the 1st contour
1457void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001458 int i, vcount = path.fPathRef->countVerbs();
1459 // exit early if the path is empty, or just has a moveTo.
1460 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461 return;
1462 }
1463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001466 fIsOval = false;
1467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 const uint8_t* verbs = path.fPathRef->verbs();
1469 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001470 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 SkASSERT(verbs[~0] == kMove_Verb);
1473 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001474 unsigned v = verbs[~i];
1475 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 if (n == 0) {
1477 break;
1478 }
1479 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001480 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 }
1482
1483 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 case kLine_Verb:
1486 this->lineTo(pts[-1].fX, pts[-1].fY);
1487 break;
1488 case kQuad_Verb:
1489 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1490 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001491 case kConic_Verb:
1492 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1493 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 case kCubic_Verb:
1495 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1496 pts[-3].fX, pts[-3].fY);
1497 break;
1498 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001499 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 break;
1501 }
reed@google.com277c3f82013-05-31 15:17:50 +00001502 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 }
1504}
1505
reed@google.com63d73742012-01-10 15:33:12 +00001506void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001508
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001509 const SkPoint* pts = src.fPathRef->pointsEnd();
1510 // we will iterator through src's verbs backwards
1511 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1512 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001513 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001514
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001515 fIsOval = false;
1516
reed@google.com63d73742012-01-10 15:33:12 +00001517 bool needMove = true;
1518 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 while (verbs < verbsEnd) {
1520 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001521 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001522
1523 if (needMove) {
1524 --pts;
1525 this->moveTo(pts->fX, pts->fY);
1526 needMove = false;
1527 }
1528 pts -= n;
1529 switch (v) {
1530 case kMove_Verb:
1531 if (needClose) {
1532 this->close();
1533 needClose = false;
1534 }
1535 needMove = true;
1536 pts += 1; // so we see the point in "if (needMove)" above
1537 break;
1538 case kLine_Verb:
1539 this->lineTo(pts[0]);
1540 break;
1541 case kQuad_Verb:
1542 this->quadTo(pts[1], pts[0]);
1543 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001544 case kConic_Verb:
1545 this->conicTo(pts[1], pts[0], *--conicWeights);
1546 break;
reed@google.com63d73742012-01-10 15:33:12 +00001547 case kCubic_Verb:
1548 this->cubicTo(pts[2], pts[1], pts[0]);
1549 break;
1550 case kClose_Verb:
1551 needClose = true;
1552 break;
1553 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001554 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001555 }
1556 }
1557}
1558
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559///////////////////////////////////////////////////////////////////////////////
1560
1561void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1562 SkMatrix matrix;
1563
1564 matrix.setTranslate(dx, dy);
1565 this->transform(matrix, dst);
1566}
1567
1568#include "SkGeometry.h"
1569
1570static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1571 int level = 2) {
1572 if (--level >= 0) {
1573 SkPoint tmp[5];
1574
1575 SkChopQuadAtHalf(pts, tmp);
1576 subdivide_quad_to(path, &tmp[0], level);
1577 subdivide_quad_to(path, &tmp[2], level);
1578 } else {
1579 path->quadTo(pts[1], pts[2]);
1580 }
1581}
1582
1583static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1584 int level = 2) {
1585 if (--level >= 0) {
1586 SkPoint tmp[7];
1587
1588 SkChopCubicAtHalf(pts, tmp);
1589 subdivide_cubic_to(path, &tmp[0], level);
1590 subdivide_cubic_to(path, &tmp[3], level);
1591 } else {
1592 path->cubicTo(pts[1], pts[2], pts[3]);
1593 }
1594}
1595
1596void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1597 SkDEBUGCODE(this->validate();)
1598 if (dst == NULL) {
1599 dst = (SkPath*)this;
1600 }
1601
tomhudson@google.com8d430182011-06-06 19:11:19 +00001602 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 SkPath tmp;
1604 tmp.fFillType = fFillType;
1605
1606 SkPath::Iter iter(*this, false);
1607 SkPoint pts[4];
1608 SkPath::Verb verb;
1609
reed@google.com4a3b7142012-05-16 17:16:46 +00001610 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 switch (verb) {
1612 case kMove_Verb:
1613 tmp.moveTo(pts[0]);
1614 break;
1615 case kLine_Verb:
1616 tmp.lineTo(pts[1]);
1617 break;
1618 case kQuad_Verb:
1619 subdivide_quad_to(&tmp, pts);
1620 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001621 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001622 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001623 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1624 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625 case kCubic_Verb:
1626 subdivide_cubic_to(&tmp, pts);
1627 break;
1628 case kClose_Verb:
1629 tmp.close();
1630 break;
1631 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001632 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633 break;
1634 }
1635 }
1636
1637 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001638 SkPathRef::Editor ed(&dst->fPathRef);
1639 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001640 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001642 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1643
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001646 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001647 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001649
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001650#ifdef SK_BUILD_FOR_ANDROID
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001651 if (!matrix.isIdentity() && !dst->hasComputedBounds()) {
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001652 GEN_ID_PTR_INC(dst);
1653 }
1654#endif
1655
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001656 if (kUnknown_Direction == fDirection) {
1657 dst->fDirection = kUnknown_Direction;
1658 } else {
1659 SkScalar det2x2 =
1660 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1661 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1662 if (det2x2 < 0) {
1663 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1664 } else if (det2x2 > 0) {
1665 dst->fDirection = fDirection;
1666 } else {
1667 dst->fDirection = kUnknown_Direction;
1668 }
1669 }
1670
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001671 // It's an oval only if it stays a rect.
1672 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001673
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 SkDEBUGCODE(dst->validate();)
1675 }
1676}
1677
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678///////////////////////////////////////////////////////////////////////////////
1679///////////////////////////////////////////////////////////////////////////////
1680
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001681enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001682 kEmptyContour_SegmentState, // The current contour is empty. We may be
1683 // starting processing or we may have just
1684 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001685 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1686 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1687 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688};
1689
1690SkPath::Iter::Iter() {
1691#ifdef SK_DEBUG
1692 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001693 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001695 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001696 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697#endif
1698 // need to init enough to make next() harmlessly return kDone_Verb
1699 fVerbs = NULL;
1700 fVerbStop = NULL;
1701 fNeedClose = false;
1702}
1703
1704SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1705 this->setPath(path, forceClose);
1706}
1707
1708void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001709 fPts = path.fPathRef->points();
1710 fVerbs = path.fPathRef->verbs();
1711 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001712 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001713 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001714 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715 fForceClose = SkToU8(forceClose);
1716 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001717 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718}
1719
1720bool SkPath::Iter::isClosedContour() const {
1721 if (fVerbs == NULL || fVerbs == fVerbStop) {
1722 return false;
1723 }
1724 if (fForceClose) {
1725 return true;
1726 }
1727
1728 const uint8_t* verbs = fVerbs;
1729 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001730
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001731 if (kMove_Verb == *(verbs - 1)) {
1732 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 }
1734
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001735 while (verbs > stop) {
1736 // verbs points one beyond the current verb, decrement first.
1737 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 if (kMove_Verb == v) {
1739 break;
1740 }
1741 if (kClose_Verb == v) {
1742 return true;
1743 }
1744 }
1745 return false;
1746}
1747
1748SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001749 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001751 // A special case: if both points are NaN, SkPoint::operation== returns
1752 // false, but the iterator expects that they are treated as the same.
1753 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001754 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1755 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001756 return kClose_Verb;
1757 }
1758
reed@google.com9e25dbf2012-05-15 17:05:38 +00001759 pts[0] = fLastPt;
1760 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 fLastPt = fMoveTo;
1762 fCloseLine = true;
1763 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001764 } else {
1765 pts[0] = fMoveTo;
1766 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768}
1769
reed@google.com9e25dbf2012-05-15 17:05:38 +00001770const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001771 if (fSegmentState == kAfterMove_SegmentState) {
1772 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001774 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001776 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1777 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001778 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780}
1781
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001782void SkPath::Iter::consumeDegenerateSegments() {
1783 // We need to step over anything that will not move the current draw point
1784 // forward before the next move is seen
1785 const uint8_t* lastMoveVerb = 0;
1786 const SkPoint* lastMovePt = 0;
1787 SkPoint lastPt = fLastPt;
1788 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001789 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001790 switch (verb) {
1791 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792 // Keep a record of this most recent move
1793 lastMoveVerb = fVerbs;
1794 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001795 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001796 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001797 fPts++;
1798 break;
1799
1800 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001801 // A close when we are in a segment is always valid except when it
1802 // follows a move which follows a segment.
1803 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 return;
1805 }
1806 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001807 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001808 break;
1809
1810 case kLine_Verb:
1811 if (!IsLineDegenerate(lastPt, fPts[0])) {
1812 if (lastMoveVerb) {
1813 fVerbs = lastMoveVerb;
1814 fPts = lastMovePt;
1815 return;
1816 }
1817 return;
1818 }
1819 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001820 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001821 fPts++;
1822 break;
1823
reed@google.com277c3f82013-05-31 15:17:50 +00001824 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 case kQuad_Verb:
1826 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1827 if (lastMoveVerb) {
1828 fVerbs = lastMoveVerb;
1829 fPts = lastMovePt;
1830 return;
1831 }
1832 return;
1833 }
1834 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001835 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001836 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001837 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 break;
1839
1840 case kCubic_Verb:
1841 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1842 if (lastMoveVerb) {
1843 fVerbs = lastMoveVerb;
1844 fPts = lastMovePt;
1845 return;
1846 }
1847 return;
1848 }
1849 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 fPts += 3;
1852 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001853
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001855 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 }
1857 }
1858}
1859
reed@google.com4a3b7142012-05-16 17:16:46 +00001860SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001861 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 // Close the curve if requested and if there is some curve to close
1865 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001866 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 return kLine_Verb;
1868 }
1869 fNeedClose = false;
1870 return kClose_Verb;
1871 }
1872 return kDone_Verb;
1873 }
1874
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001875 // fVerbs is one beyond the current verb, decrement first
1876 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001877 const SkPoint* SK_RESTRICT srcPts = fPts;
1878 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879
1880 switch (verb) {
1881 case kMove_Verb:
1882 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 verb = this->autoClose(pts);
1885 if (verb == kClose_Verb) {
1886 fNeedClose = false;
1887 }
1888 return (Verb)verb;
1889 }
1890 if (fVerbs == fVerbStop) { // might be a trailing moveto
1891 return kDone_Verb;
1892 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001893 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001894 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001896 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 fNeedClose = fForceClose;
1899 break;
1900 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 pts[0] = this->cons_moveTo();
1902 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 fLastPt = srcPts[0];
1904 fCloseLine = false;
1905 srcPts += 1;
1906 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001907 case kConic_Verb:
1908 fConicWeights += 1;
1909 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001911 pts[0] = this->cons_moveTo();
1912 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 fLastPt = srcPts[1];
1914 srcPts += 2;
1915 break;
1916 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001917 pts[0] = this->cons_moveTo();
1918 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 fLastPt = srcPts[2];
1920 srcPts += 3;
1921 break;
1922 case kClose_Verb:
1923 verb = this->autoClose(pts);
1924 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001925 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926 } else {
1927 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001928 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 break;
1932 }
1933 fPts = srcPts;
1934 return (Verb)verb;
1935}
1936
1937///////////////////////////////////////////////////////////////////////////////
1938
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001939SkPath::RawIter::RawIter() {
1940#ifdef SK_DEBUG
1941 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001942 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001943 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1944#endif
1945 // need to init enough to make next() harmlessly return kDone_Verb
1946 fVerbs = NULL;
1947 fVerbStop = NULL;
1948}
1949
1950SkPath::RawIter::RawIter(const SkPath& path) {
1951 this->setPath(path);
1952}
1953
1954void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fPts = path.fPathRef->points();
1956 fVerbs = path.fPathRef->verbs();
1957 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001958 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001959 fMoveTo.fX = fMoveTo.fY = 0;
1960 fLastPt.fX = fLastPt.fY = 0;
1961}
1962
1963SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001964 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001965 if (fVerbs == fVerbStop) {
1966 return kDone_Verb;
1967 }
1968
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001969 // fVerbs points one beyond next verb so decrement first.
1970 unsigned verb = *(--fVerbs);
1971 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001972
1973 switch (verb) {
1974 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001975 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001976 fMoveTo = srcPts[0];
1977 fLastPt = fMoveTo;
1978 srcPts += 1;
1979 break;
1980 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001981 pts[0] = fLastPt;
1982 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001983 fLastPt = srcPts[0];
1984 srcPts += 1;
1985 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001986 case kConic_Verb:
1987 fConicWeights += 1;
1988 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001989 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001990 pts[0] = fLastPt;
1991 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992 fLastPt = srcPts[1];
1993 srcPts += 2;
1994 break;
1995 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001996 pts[0] = fLastPt;
1997 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001998 fLastPt = srcPts[2];
1999 srcPts += 3;
2000 break;
2001 case kClose_Verb:
2002 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002003 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002004 break;
2005 }
2006 fPts = srcPts;
2007 return (Verb)verb;
2008}
2009
2010///////////////////////////////////////////////////////////////////////////////
2011
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002013 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014*/
2015
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002016uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017 SkDEBUGCODE(this->validate();)
2018
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002019 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002020 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002021 return SkAlign4(byteCount);
2022 }
2023
2024 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002025
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002026 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002027 (fConvexity << kConvexity_SerializationShift) |
2028 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002029 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002030 (fDirection << kDirection_SerializationShift)
2031#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2032 | (0x1 << kNewFormat_SerializationShift);
2033#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002034
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002035 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002036
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002037 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002038
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002039 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002040 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041}
2042
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002043uint32_t SkPath::readFromMemory(const void* storage) {
2044 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002045
reed@google.com98b11f12011-09-21 18:40:27 +00002046 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002047 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2048 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2049 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002050 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002051 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002052#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2053 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2054#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002055
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002056 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2057#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2058 , newFormat, packed)
2059#endif
2060 );
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002061
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002062 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002063
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002064 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065
2066 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002067 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068}
2069
2070///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071
reed@google.com51bbe752013-01-17 22:07:50 +00002072#include "SkString.h"
2073
2074static void append_scalar(SkString* str, SkScalar value) {
2075 SkString tmp;
2076 tmp.printf("%g", value);
2077 if (tmp.contains('.')) {
2078 tmp.appendUnichar('f');
2079 }
2080 str->append(tmp);
2081}
2082
2083static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002084 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002085 str->append(label);
2086 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002087
reed@google.com51bbe752013-01-17 22:07:50 +00002088 const SkScalar* values = &pts[0].fX;
2089 count *= 2;
2090
2091 for (int i = 0; i < count; ++i) {
2092 append_scalar(str, values[i]);
2093 if (i < count - 1) {
2094 str->append(", ");
2095 }
2096 }
reed@google.com277c3f82013-05-31 15:17:50 +00002097 if (conicWeight >= 0) {
2098 str->append(", ");
2099 append_scalar(str, conicWeight);
2100 }
reed@google.com51bbe752013-01-17 22:07:50 +00002101 str->append(");\n");
2102}
2103
reed@android.com8a1c16f2008-12-17 15:59:43 +00002104void SkPath::dump(bool forceClose, const char title[]) const {
2105 Iter iter(*this, forceClose);
2106 SkPoint pts[4];
2107 Verb verb;
2108
2109 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2110 title ? title : "");
2111
reed@google.com51bbe752013-01-17 22:07:50 +00002112 SkString builder;
2113
reed@google.com4a3b7142012-05-16 17:16:46 +00002114 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 switch (verb) {
2116 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002117 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 break;
2119 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002120 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 break;
2122 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002123 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002125 case kConic_Verb:
2126 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2127 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002129 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 break;
2131 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002132 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 break;
2134 default:
2135 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2136 verb = kDone_Verb; // stop the loop
2137 break;
2138 }
2139 }
reed@google.com51bbe752013-01-17 22:07:50 +00002140 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141}
2142
reed@android.come522ca52009-11-23 20:10:41 +00002143void SkPath::dump() const {
2144 this->dump(false);
2145}
2146
2147#ifdef SK_DEBUG
2148void SkPath::validate() const {
2149 SkASSERT(this != NULL);
2150 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002151
djsollen@google.com077348c2012-10-22 20:23:32 +00002152#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002153 if (!fBoundsIsDirty) {
2154 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002155
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002156 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002157 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002158
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002159 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002160 // if we're empty, fBounds may be empty but translated, so we can't
2161 // necessarily compare to bounds directly
2162 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2163 // be [2, 2, 2, 2]
2164 SkASSERT(bounds.isEmpty());
2165 SkASSERT(fBounds.isEmpty());
2166 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002167 if (bounds.isEmpty()) {
2168 SkASSERT(fBounds.isEmpty());
2169 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002170 if (!fBounds.isEmpty()) {
2171 SkASSERT(fBounds.contains(bounds));
2172 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002173 }
reed@android.come522ca52009-11-23 20:10:41 +00002174 }
2175 }
reed@google.com10296cc2011-09-21 12:29:05 +00002176
2177 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002178 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2179 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2180 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002181 case kLine_Verb:
2182 mask |= kLine_SegmentMask;
2183 break;
2184 case kQuad_Verb:
2185 mask |= kQuad_SegmentMask;
2186 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002187 case kConic_Verb:
2188 mask |= kConic_SegmentMask;
2189 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002190 case kCubic_Verb:
2191 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002192 case kMove_Verb: // these verbs aren't included in the segment mask.
2193 case kClose_Verb:
2194 break;
2195 case kDone_Verb:
2196 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2197 break;
2198 default:
2199 SkDEBUGFAIL("Unknown Verb");
2200 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002201 }
2202 }
2203 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002204#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002205}
djsollen@google.com077348c2012-10-22 20:23:32 +00002206#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002207
reed@google.com04863fa2011-05-15 04:08:24 +00002208///////////////////////////////////////////////////////////////////////////////
2209
reed@google.com0b7b9822011-05-16 12:29:27 +00002210static int sign(SkScalar x) { return x < 0; }
2211#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002212
reed@google.com04863fa2011-05-15 04:08:24 +00002213static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002214 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002215}
2216
2217// only valid for a single contour
2218struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002219 Convexicator()
2220 : fPtCount(0)
2221 , fConvexity(SkPath::kConvex_Convexity)
2222 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002223 fSign = 0;
2224 // warnings
2225 fCurrPt.set(0, 0);
2226 fVec0.set(0, 0);
2227 fVec1.set(0, 0);
2228 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002229
2230 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002231 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002232 }
2233
2234 SkPath::Convexity getConvexity() const { return fConvexity; }
2235
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002236 /** The direction returned is only valid if the path is determined convex */
2237 SkPath::Direction getDirection() const { return fDirection; }
2238
reed@google.com04863fa2011-05-15 04:08:24 +00002239 void addPt(const SkPoint& pt) {
2240 if (SkPath::kConcave_Convexity == fConvexity) {
2241 return;
2242 }
2243
2244 if (0 == fPtCount) {
2245 fCurrPt = pt;
2246 ++fPtCount;
2247 } else {
2248 SkVector vec = pt - fCurrPt;
2249 if (vec.fX || vec.fY) {
2250 fCurrPt = pt;
2251 if (++fPtCount == 2) {
2252 fFirstVec = fVec1 = vec;
2253 } else {
2254 SkASSERT(fPtCount > 2);
2255 this->addVec(vec);
2256 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002257
reed@google.com85b6e392011-05-15 20:25:17 +00002258 int sx = sign(vec.fX);
2259 int sy = sign(vec.fY);
2260 fDx += (sx != fSx);
2261 fDy += (sy != fSy);
2262 fSx = sx;
2263 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002264
reed@google.com85b6e392011-05-15 20:25:17 +00002265 if (fDx > 3 || fDy > 3) {
2266 fConvexity = SkPath::kConcave_Convexity;
2267 }
reed@google.com04863fa2011-05-15 04:08:24 +00002268 }
2269 }
2270 }
2271
2272 void close() {
2273 if (fPtCount > 2) {
2274 this->addVec(fFirstVec);
2275 }
2276 }
2277
2278private:
2279 void addVec(const SkVector& vec) {
2280 SkASSERT(vec.fX || vec.fY);
2281 fVec0 = fVec1;
2282 fVec1 = vec;
2283 int sign = CrossProductSign(fVec0, fVec1);
2284 if (0 == fSign) {
2285 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002286 if (1 == sign) {
2287 fDirection = SkPath::kCW_Direction;
2288 } else if (-1 == sign) {
2289 fDirection = SkPath::kCCW_Direction;
2290 }
reed@google.com04863fa2011-05-15 04:08:24 +00002291 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002292 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002293 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002294 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 }
2296 }
2297 }
2298
2299 SkPoint fCurrPt;
2300 SkVector fVec0, fVec1, fFirstVec;
2301 int fPtCount; // non-degenerate points
2302 int fSign;
2303 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002304 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002305 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002306};
2307
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002308SkPath::Convexity SkPath::internalGetConvexity() const {
2309 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002310 SkPoint pts[4];
2311 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002312 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002313
2314 int contourCount = 0;
2315 int count;
2316 Convexicator state;
2317
2318 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2319 switch (verb) {
2320 case kMove_Verb:
2321 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002322 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002323 return kConcave_Convexity;
2324 }
2325 pts[1] = pts[0];
2326 count = 1;
2327 break;
2328 case kLine_Verb: count = 1; break;
2329 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002330 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002331 case kCubic_Verb: count = 3; break;
2332 case kClose_Verb:
2333 state.close();
2334 count = 0;
2335 break;
2336 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002337 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002338 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002339 return kConcave_Convexity;
2340 }
2341
2342 for (int i = 1; i <= count; i++) {
2343 state.addPt(pts[i]);
2344 }
2345 // early exit
2346 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002347 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002348 return kConcave_Convexity;
2349 }
2350 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002351 fConvexity = state.getConvexity();
2352 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2353 fDirection = state.getDirection();
2354 }
2355 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002356}
reed@google.com69a99432012-01-10 18:00:10 +00002357
2358///////////////////////////////////////////////////////////////////////////////
2359
2360class ContourIter {
2361public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002362 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002363
2364 bool done() const { return fDone; }
2365 // if !done() then these may be called
2366 int count() const { return fCurrPtCount; }
2367 const SkPoint* pts() const { return fCurrPt; }
2368 void next();
2369
2370private:
2371 int fCurrPtCount;
2372 const SkPoint* fCurrPt;
2373 const uint8_t* fCurrVerb;
2374 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002375 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002376 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002377 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002378};
2379
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002380ContourIter::ContourIter(const SkPathRef& pathRef) {
2381 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002382 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002383 fCurrPt = pathRef.points();
2384 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002385 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002386 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002387 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002388 this->next();
2389}
2390
2391void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002392 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002393 fDone = true;
2394 }
2395 if (fDone) {
2396 return;
2397 }
2398
2399 // skip pts of prev contour
2400 fCurrPt += fCurrPtCount;
2401
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002402 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002403 int ptCount = 1; // moveTo
2404 const uint8_t* verbs = fCurrVerb;
2405
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002406 for (--verbs; verbs > fStopVerbs; --verbs) {
2407 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002408 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002409 goto CONTOUR_END;
2410 case SkPath::kLine_Verb:
2411 ptCount += 1;
2412 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002413 case SkPath::kConic_Verb:
2414 fCurrConicWeight += 1;
2415 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002416 case SkPath::kQuad_Verb:
2417 ptCount += 2;
2418 break;
2419 case SkPath::kCubic_Verb:
2420 ptCount += 3;
2421 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002422 case SkPath::kClose_Verb:
2423 break;
2424 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002425 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002426 break;
2427 }
2428 }
2429CONTOUR_END:
2430 fCurrPtCount = ptCount;
2431 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002432 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002433}
2434
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002435// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002436static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002437 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2438 // We may get 0 when the above subtracts underflow. We expect this to be
2439 // very rare and lazily promote to double.
2440 if (0 == cross) {
2441 double p0x = SkScalarToDouble(p0.fX);
2442 double p0y = SkScalarToDouble(p0.fY);
2443
2444 double p1x = SkScalarToDouble(p1.fX);
2445 double p1y = SkScalarToDouble(p1.fY);
2446
2447 double p2x = SkScalarToDouble(p2.fX);
2448 double p2y = SkScalarToDouble(p2.fY);
2449
2450 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2451 (p1y - p0y) * (p2x - p0x));
2452
2453 }
2454 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002455}
2456
reed@google.comc1ea60a2012-01-31 15:15:36 +00002457// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002458static int find_max_y(const SkPoint pts[], int count) {
2459 SkASSERT(count > 0);
2460 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002461 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002462 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002463 SkScalar y = pts[i].fY;
2464 if (y > max) {
2465 max = y;
2466 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002467 }
2468 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002469 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002470}
2471
reed@google.comcabaf1d2012-01-11 21:03:05 +00002472static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2473 int i = index;
2474 for (;;) {
2475 i = (i + inc) % n;
2476 if (i == index) { // we wrapped around, so abort
2477 break;
2478 }
2479 if (pts[index] != pts[i]) { // found a different point, success!
2480 break;
2481 }
2482 }
2483 return i;
2484}
2485
reed@google.comc1ea60a2012-01-31 15:15:36 +00002486/**
2487 * Starting at index, and moving forward (incrementing), find the xmin and
2488 * xmax of the contiguous points that have the same Y.
2489 */
2490static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2491 int* maxIndexPtr) {
2492 const SkScalar y = pts[index].fY;
2493 SkScalar min = pts[index].fX;
2494 SkScalar max = min;
2495 int minIndex = index;
2496 int maxIndex = index;
2497 for (int i = index + 1; i < n; ++i) {
2498 if (pts[i].fY != y) {
2499 break;
2500 }
2501 SkScalar x = pts[i].fX;
2502 if (x < min) {
2503 min = x;
2504 minIndex = i;
2505 } else if (x > max) {
2506 max = x;
2507 maxIndex = i;
2508 }
2509 }
2510 *maxIndexPtr = maxIndex;
2511 return minIndex;
2512}
2513
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002514static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002515 if (dir) {
2516 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2517 }
reed@google.comac8543f2012-01-30 20:51:25 +00002518}
2519
reed@google.comc1ea60a2012-01-31 15:15:36 +00002520#if 0
2521#include "SkString.h"
2522#include "../utils/SkParsePath.h"
2523static void dumpPath(const SkPath& path) {
2524 SkString str;
2525 SkParsePath::ToSVGString(path, &str);
2526 SkDebugf("%s\n", str.c_str());
2527}
2528#endif
2529
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002530namespace {
2531// for use with convex_dir_test
2532double mul(double a, double b) { return a * b; }
2533SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2534double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2535SkScalar toScalar(SkScalar a) { return a; }
2536
2537// determines the winding direction of a convex polygon with the precision
2538// of T. CAST_SCALAR casts an SkScalar to T.
2539template <typename T, T (CAST_SCALAR)(SkScalar)>
2540bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2541 // we find the first three points that form a non-degenerate
2542 // triangle. If there are no such points then the path is
2543 // degenerate. The first is always point 0. Now we find the second
2544 // point.
2545 int i = 0;
2546 enum { kX = 0, kY = 1 };
2547 T v0[2];
2548 while (1) {
2549 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2550 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2551 if (v0[kX] || v0[kY]) {
2552 break;
2553 }
2554 if (++i == n - 1) {
2555 return false;
2556 }
2557 }
2558 // now find a third point that is not colinear with the first two
2559 // points and check the orientation of the triangle (which will be
2560 // the same as the orientation of the path).
2561 for (++i; i < n; ++i) {
2562 T v1[2];
2563 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2564 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2565 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2566 if (0 != cross) {
2567 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2568 return true;
2569 }
2570 }
2571 return false;
2572}
2573}
2574
reed@google.comac8543f2012-01-30 20:51:25 +00002575/*
2576 * We loop through all contours, and keep the computed cross-product of the
2577 * contour that contained the global y-max. If we just look at the first
2578 * contour, we may find one that is wound the opposite way (correctly) since
2579 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2580 * that is outer most (or at least has the global y-max) before we can consider
2581 * its cross product.
2582 */
reed@google.com69a99432012-01-10 18:00:10 +00002583bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002584// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002585 // don't want to pay the cost for computing this if it
2586 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002587
2588 if (kUnknown_Direction != fDirection) {
2589 *dir = static_cast<Direction>(fDirection);
2590 return true;
2591 }
reed@google.com69a99432012-01-10 18:00:10 +00002592 const Convexity conv = this->getConvexityOrUnknown();
2593
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002594 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002595
reed@google.comac8543f2012-01-30 20:51:25 +00002596 // initialize with our logical y-min
2597 SkScalar ymax = this->getBounds().fTop;
2598 SkScalar ymaxCross = 0;
2599
reed@google.com69a99432012-01-10 18:00:10 +00002600 for (; !iter.done(); iter.next()) {
2601 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002602 if (n < 3) {
2603 continue;
2604 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002605
reed@google.comcabaf1d2012-01-11 21:03:05 +00002606 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002607 SkScalar cross = 0;
2608 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002609 // We try first at scalar precision, and then again at double
2610 // precision. This is because the vectors computed between distant
2611 // points may lose too much precision.
2612 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002613 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002614 return true;
2615 }
2616 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002617 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002618 return true;
2619 } else {
2620 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002621 }
2622 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002623 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002624 if (pts[index].fY < ymax) {
2625 continue;
2626 }
2627
reed@google.comc1ea60a2012-01-31 15:15:36 +00002628 // If there is more than 1 distinct point at the y-max, we take the
2629 // x-min and x-max of them and just subtract to compute the dir.
2630 if (pts[(index + 1) % n].fY == pts[index].fY) {
2631 int maxIndex;
2632 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002633 if (minIndex == maxIndex) {
2634 goto TRY_CROSSPROD;
2635 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002636 SkASSERT(pts[minIndex].fY == pts[index].fY);
2637 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2638 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2639 // we just subtract the indices, and let that auto-convert to
2640 // SkScalar, since we just want - or + to signal the direction.
2641 cross = minIndex - maxIndex;
2642 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002643 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002644 // Find a next and prev index to use for the cross-product test,
2645 // but we try to find pts that form non-zero vectors from pts[index]
2646 //
2647 // Its possible that we can't find two non-degenerate vectors, so
2648 // we have to guard our search (e.g. all the pts could be in the
2649 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002650
reed@google.comc1ea60a2012-01-31 15:15:36 +00002651 // we pass n - 1 instead of -1 so we don't foul up % operator by
2652 // passing it a negative LH argument.
2653 int prev = find_diff_pt(pts, index, n, n - 1);
2654 if (prev == index) {
2655 // completely degenerate, skip to next contour
2656 continue;
2657 }
2658 int next = find_diff_pt(pts, index, n, 1);
2659 SkASSERT(next != index);
2660 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002661 // 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 +00002662 // x-direction. We really should continue to walk away from the degeneracy until
2663 // there is a divergence.
2664 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002665 // construct the subtract so we get the correct Direction below
2666 cross = pts[index].fX - pts[next].fX;
2667 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002668 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002669
reed@google.comac8543f2012-01-30 20:51:25 +00002670 if (cross) {
2671 // record our best guess so far
2672 ymax = pts[index].fY;
2673 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002674 }
reed@google.com69a99432012-01-10 18:00:10 +00002675 }
2676 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002677 if (ymaxCross) {
2678 crossToDir(ymaxCross, dir);
2679 fDirection = *dir;
2680 return true;
2681 } else {
2682 return false;
2683 }
reed@google.comac8543f2012-01-30 20:51:25 +00002684}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002685
2686///////////////////////////////////////////////////////////////////////////////
2687
2688static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2689 SkScalar D, SkScalar t) {
2690 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2691}
2692
2693static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2694 SkScalar t) {
2695 SkScalar A = c3 + 3*(c1 - c2) - c0;
2696 SkScalar B = 3*(c2 - c1 - c1 + c0);
2697 SkScalar C = 3*(c1 - c0);
2698 SkScalar D = c0;
2699 return eval_cubic_coeff(A, B, C, D, t);
2700}
2701
2702/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2703 t value such that cubic(t) = target
2704 */
2705static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2706 SkScalar target, SkScalar* t) {
2707 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2708 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002709
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002710 SkScalar D = c0 - target;
2711 SkScalar A = c3 + 3*(c1 - c2) - c0;
2712 SkScalar B = 3*(c2 - c1 - c1 + c0);
2713 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002714
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002715 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2716 SkScalar minT = 0;
2717 SkScalar maxT = SK_Scalar1;
2718 SkScalar mid;
2719 int i;
2720 for (i = 0; i < 16; i++) {
2721 mid = SkScalarAve(minT, maxT);
2722 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2723 if (delta < 0) {
2724 minT = mid;
2725 delta = -delta;
2726 } else {
2727 maxT = mid;
2728 }
2729 if (delta < TOLERANCE) {
2730 break;
2731 }
2732 }
2733 *t = mid;
2734 return true;
2735}
2736
2737template <size_t N> static void find_minmax(const SkPoint pts[],
2738 SkScalar* minPtr, SkScalar* maxPtr) {
2739 SkScalar min, max;
2740 min = max = pts[0].fX;
2741 for (size_t i = 1; i < N; ++i) {
2742 min = SkMinScalar(min, pts[i].fX);
2743 max = SkMaxScalar(max, pts[i].fX);
2744 }
2745 *minPtr = min;
2746 *maxPtr = max;
2747}
2748
2749static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2750 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002751
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752 int dir = 1;
2753 if (pts[0].fY > pts[3].fY) {
2754 storage[0] = pts[3];
2755 storage[1] = pts[2];
2756 storage[2] = pts[1];
2757 storage[3] = pts[0];
2758 pts = storage;
2759 dir = -1;
2760 }
2761 if (y < pts[0].fY || y >= pts[3].fY) {
2762 return 0;
2763 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 // quickreject or quickaccept
2766 SkScalar min, max;
2767 find_minmax<4>(pts, &min, &max);
2768 if (x < min) {
2769 return 0;
2770 }
2771 if (x > max) {
2772 return dir;
2773 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002774
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002775 // compute the actual x(t) value
2776 SkScalar t, xt;
2777 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2778 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2779 } else {
2780 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2781 xt = y < mid ? pts[0].fX : pts[3].fX;
2782 }
2783 return xt < x ? dir : 0;
2784}
2785
2786static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2787 SkPoint dst[10];
2788 int n = SkChopCubicAtYExtrema(pts, dst);
2789 int w = 0;
2790 for (int i = 0; i <= n; ++i) {
2791 w += winding_mono_cubic(&dst[i * 3], x, y);
2792 }
2793 return w;
2794}
2795
2796static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2797 SkScalar y0 = pts[0].fY;
2798 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002799
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 int dir = 1;
2801 if (y0 > y2) {
2802 SkTSwap(y0, y2);
2803 dir = -1;
2804 }
2805 if (y < y0 || y >= y2) {
2806 return 0;
2807 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 // bounds check on X (not required. is it faster?)
2810#if 0
2811 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2812 return 0;
2813 }
2814#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002815
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816 SkScalar roots[2];
2817 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2818 2 * (pts[1].fY - pts[0].fY),
2819 pts[0].fY - y,
2820 roots);
2821 SkASSERT(n <= 1);
2822 SkScalar xt;
2823 if (0 == n) {
2824 SkScalar mid = SkScalarAve(y0, y2);
2825 // Need [0] and [2] if dir == 1
2826 // and [2] and [0] if dir == -1
2827 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2828 } else {
2829 SkScalar t = roots[0];
2830 SkScalar C = pts[0].fX;
2831 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2832 SkScalar B = 2 * (pts[1].fX - C);
2833 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2834 }
2835 return xt < x ? dir : 0;
2836}
2837
2838static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2839 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2840 if (y0 == y1) {
2841 return true;
2842 }
2843 if (y0 < y1) {
2844 return y1 <= y2;
2845 } else {
2846 return y1 >= y2;
2847 }
2848}
2849
2850static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2851 SkPoint dst[5];
2852 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002853
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002854 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2855 n = SkChopQuadAtYExtrema(pts, dst);
2856 pts = dst;
2857 }
2858 int w = winding_mono_quad(pts, x, y);
2859 if (n > 0) {
2860 w += winding_mono_quad(&pts[2], x, y);
2861 }
2862 return w;
2863}
2864
2865static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2866 SkScalar x0 = pts[0].fX;
2867 SkScalar y0 = pts[0].fY;
2868 SkScalar x1 = pts[1].fX;
2869 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002870
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002871 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002872
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 int dir = 1;
2874 if (y0 > y1) {
2875 SkTSwap(y0, y1);
2876 dir = -1;
2877 }
2878 if (y < y0 || y >= y1) {
2879 return 0;
2880 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002881
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002882 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2883 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002884
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 if (SkScalarSignAsInt(cross) == dir) {
2886 dir = 0;
2887 }
2888 return dir;
2889}
2890
2891bool SkPath::contains(SkScalar x, SkScalar y) const {
2892 bool isInverse = this->isInverseFillType();
2893 if (this->isEmpty()) {
2894 return isInverse;
2895 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002896
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002897 const SkRect& bounds = this->getBounds();
2898 if (!bounds.contains(x, y)) {
2899 return isInverse;
2900 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002901
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002902 SkPath::Iter iter(*this, true);
2903 bool done = false;
2904 int w = 0;
2905 do {
2906 SkPoint pts[4];
2907 switch (iter.next(pts, false)) {
2908 case SkPath::kMove_Verb:
2909 case SkPath::kClose_Verb:
2910 break;
2911 case SkPath::kLine_Verb:
2912 w += winding_line(pts, x, y);
2913 break;
2914 case SkPath::kQuad_Verb:
2915 w += winding_quad(pts, x, y);
2916 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002917 case SkPath::kConic_Verb:
2918 SkASSERT(0);
2919 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 case SkPath::kCubic_Verb:
2921 w += winding_cubic(pts, x, y);
2922 break;
2923 case SkPath::kDone_Verb:
2924 done = true;
2925 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002926 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002927 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002928
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 switch (this->getFillType()) {
2930 case SkPath::kEvenOdd_FillType:
2931 case SkPath::kInverseEvenOdd_FillType:
2932 w &= 1;
2933 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002934 default:
2935 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 }
2937 return SkToBool(w);
2938}