blob: 9df62850fd2eb5bc9cf442dc4675b7091cded8c1 [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
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000150 , fGenerationID(0)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000151#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;
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000164#ifdef SK_BUILD_FOR_ANDROID
165 GEN_ID_INC;
166 // 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.
168#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
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000175 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
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000192 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
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000235 // 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.
237 GEN_ID_INC;
238 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
commit-bot@chromium.org4cc75182013-10-29 21:34:55 +0000331#ifdef SK_BUILD_FOR_ANDROID
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000332uint32_t SkPath::getGenerationID() const {
333 return fGenerationID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000334}
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);
robertphillips@google.com7101abe2013-10-29 22:45:37 +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;
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000643 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000644 }
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);
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000672
673 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
robertphillips@google.com7101abe2013-10-29 22:45:37 +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
robertphillips@google.com7101abe2013-10-29 22:45:37 +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
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000759 GEN_ID_INC;
reed@google.com277c3f82013-05-31 15:17:50 +0000760 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
robertphillips@google.com7101abe2013-10-29 22:45:37 +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);
robertphillips@google.com7101abe2013-10-29 22:45:37 +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
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000897 GEN_ID_INC;
reed@google.com744faba2012-05-29 19:54:52 +0000898 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000899 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000900}
901
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000902#include "SkGeometry.h"
903
904static int build_arc_points(const SkRect& oval, SkScalar startAngle,
905 SkScalar sweepAngle,
906 SkPoint pts[kSkBuildQuadArcStorage]) {
907
908 if (0 == sweepAngle &&
909 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
910 // Chrome uses this path to move into and out of ovals. If not
911 // treated as a special case the moves can distort the oval's
912 // bounding box (and break the circle special case).
913 pts[0].set(oval.fRight, oval.centerY());
914 return 1;
915 } else if (0 == oval.width() && 0 == oval.height()) {
916 // Chrome will sometimes create 0 radius round rects. Having degenerate
917 // quad segments in the path prevents the path from being recognized as
918 // a rect.
919 // TODO: optimizing the case where only one of width or height is zero
920 // should also be considered. This case, however, doesn't seem to be
921 // as common as the single point case.
922 pts[0].set(oval.fRight, oval.fTop);
923 return 1;
924 }
925
926 SkVector start, stop;
927
928 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
929 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
930 &stop.fX);
931
932 /* If the sweep angle is nearly (but less than) 360, then due to precision
933 loss in radians-conversion and/or sin/cos, we may end up with coincident
934 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
935 of drawing a nearly complete circle (good).
936 e.g. canvas.drawArc(0, 359.99, ...)
937 -vs- canvas.drawArc(0, 359.9, ...)
938 We try to detect this edge case, and tweak the stop vector
939 */
940 if (start == stop) {
941 SkScalar sw = SkScalarAbs(sweepAngle);
942 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
943 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
944 // make a guess at a tiny angle (in radians) to tweak by
945 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
946 // not sure how much will be enough, so we use a loop
947 do {
948 stopRad -= deltaRad;
949 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
950 } while (start == stop);
951 }
952 }
953
954 SkMatrix matrix;
955
956 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
957 matrix.postTranslate(oval.centerX(), oval.centerY());
958
959 return SkBuildQuadArc(start, stop,
960 sweepAngle > 0 ? kCW_SkRotationDirection :
961 kCCW_SkRotationDirection,
962 &matrix, pts);
963}
964
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965static void add_corner_arc(SkPath* path, const SkRect& rect,
966 SkScalar rx, SkScalar ry, int startAngle,
robertphillips@google.com12367192013-10-20 13:11:16 +0000967 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000968 // These two asserts are not sufficient, since really we want to know
969 // that the pair of radii (e.g. left and right, or top and bottom) sum
970 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000971 // these conservative asserts.
972 SkASSERT(0 <= rx && rx <= rect.width());
973 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000974
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 SkRect r;
976 r.set(-rx, -ry, rx, ry);
977
978 switch (startAngle) {
979 case 0:
980 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
981 break;
982 case 90:
983 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
984 break;
985 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
986 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000987 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 }
reed@google.comabf15c12011-01-18 20:35:51 +0000989
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990 SkScalar start = SkIntToScalar(startAngle);
991 SkScalar sweep = SkIntToScalar(90);
992 if (SkPath::kCCW_Direction == dir) {
993 start += sweep;
994 sweep = -sweep;
995 }
reed@google.comabf15c12011-01-18 20:35:51 +0000996
robertphillips@google.com12367192013-10-20 13:11:16 +0000997 path->arcTo(r, start, sweep, forceMoveTo);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000998}
999
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001000void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001002 SkRRect rrect;
1003 rrect.setRectRadii(rect, (const SkVector*) radii);
1004 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005}
1006
reed@google.com4ed0fb72012-12-12 20:48:18 +00001007void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001008 assert_known_direction(dir);
1009
1010 if (rrect.isEmpty()) {
1011 return;
1012 }
1013
reed@google.com4ed0fb72012-12-12 20:48:18 +00001014 const SkRect& bounds = rrect.getBounds();
1015
1016 if (rrect.isRect()) {
1017 this->addRect(bounds, dir);
1018 } else if (rrect.isOval()) {
1019 this->addOval(bounds, dir);
1020 } else if (rrect.isSimple()) {
1021 const SkVector& rad = rrect.getSimpleRadii();
1022 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1023 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001024 SkAutoPathBoundsUpdate apbu(this, bounds);
1025
1026 if (kCW_Direction == dir) {
robertphillips@google.com12367192013-10-20 13:11:16 +00001027 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1028 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1029 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1030 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001031 } else {
robertphillips@google.com12367192013-10-20 13:11:16 +00001032 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1033 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1034 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1035 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001036 }
1037 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001038 }
1039}
1040
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001041bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001042 int count = fPathRef->countVerbs();
1043 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1044 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001045 if (*verbs == kLine_Verb ||
1046 *verbs == kQuad_Verb ||
1047 *verbs == kCubic_Verb) {
1048 return false;
1049 }
1050 ++verbs;
1051 }
1052 return true;
1053}
1054
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001056#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001057#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001058
1059void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1060 Direction dir) {
1061 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001062
humper@google.com75e3ca12013-04-08 21:44:11 +00001063 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001064 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001065 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001066 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001067 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1068 return;
1069 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001070
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001071 SkScalar w = rect.width();
1072 SkScalar halfW = SkScalarHalf(w);
1073 SkScalar h = rect.height();
1074 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001075
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001076 if (halfW <= 0 || halfH <= 0) {
1077 return;
1078 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001079
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001080 bool skip_hori = rx >= halfW;
1081 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001082
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001083 if (skip_hori && skip_vert) {
1084 this->addOval(rect, dir);
1085 return;
1086 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001087
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001088 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001089
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001090 SkAutoPathBoundsUpdate apbu(this, rect);
1091 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001092
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001093 if (skip_hori) {
1094 rx = halfW;
1095 } else if (skip_vert) {
1096 ry = halfH;
1097 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001098
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001100 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1101 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001102
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001103 this->incReserve(17);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001104#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001105 // Please see SkBuildQuadArc for more information but, we need to pull
1106 // the off-curve quadratic points in a little bit to make the round
1107 // rects convex.
1108 static const SkScalar kOffCurveEpsilon = 0.0001f;
1109
1110 SkScalar midPtX = rx * SK_ScalarRoot2Over2;
1111 SkScalar midPtY = ry * SK_ScalarRoot2Over2;
1112
1113 SkScalar offPtX = rx * SK_ScalarTanPIOver8 - kOffCurveEpsilon;
1114 SkScalar offPtY = ry * SK_ScalarTanPIOver8 - kOffCurveEpsilon;
1115
1116 this->incReserve(21);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001117#endif
robertphillips@google.com12367192013-10-20 13:11:16 +00001118 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001119 if (dir == kCCW_Direction) {
1120 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001121 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001122 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001123#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001124 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1125 rect.fLeft, rect.fTop + ry - sy,
1126 rect.fLeft, rect.fTop + ry); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001127#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001128 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop + kOffCurveEpsilon,
1129 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
1130 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fTop + ry - offPtY,
1131 rect.fLeft, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001132#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001133 if (!skip_vert) {
1134 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1135 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001136#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001137 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1138 rect.fLeft + rx - sx, rect.fBottom,
1139 rect.fLeft + rx, rect.fBottom); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001140#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001141 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1142 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
1143 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom - kOffCurveEpsilon,
1144 rect.fLeft + rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001145#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001146 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001147 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001148 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001149#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001150 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1151 rect.fRight, rect.fBottom - ry + sy,
1152 rect.fRight, rect.fBottom - ry); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001153#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001154 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom - kOffCurveEpsilon,
1155 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
1156 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1157 rect.fRight, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001158#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001159 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001160 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001161 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001162#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001163 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1164 rect.fRight - rx + sx, rect.fTop,
1165 rect.fRight - rx, rect.fTop); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001166#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001167 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fTop + ry - offPtY,
1168 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
1169 this->quadTo(rect.fRight - rx + offPtX, rect.fTop + kOffCurveEpsilon,
1170 rect.fRight - rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001171#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001172 } else {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001173#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001174 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1175 rect.fRight, rect.fTop + ry - sy,
1176 rect.fRight, rect.fTop + ry); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001177#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001178 this->quadTo(rect.fRight - rx + offPtX, rect.fTop + kOffCurveEpsilon,
1179 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
1180 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fTop + ry - offPtY,
1181 rect.fRight, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001182#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001183 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001184 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001185 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001186#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001187 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1188 rect.fRight - rx + sx, rect.fBottom,
1189 rect.fRight - rx, rect.fBottom); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001190#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001191 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1192 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
1193 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom - kOffCurveEpsilon,
1194 rect.fRight - rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001195#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001196 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001197 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001198 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001199#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001200 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1201 rect.fLeft, rect.fBottom - ry + sy,
1202 rect.fLeft, rect.fBottom - ry); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001203#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001204 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom - kOffCurveEpsilon,
1205 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
1206 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1207 rect.fLeft, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001208#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001209 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001210 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001211 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001212#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001213 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1214 rect.fLeft + rx - sx, rect.fTop,
1215 rect.fLeft + rx, rect.fTop); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001216#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001217 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fTop + ry - offPtY,
1218 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
1219 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop + kOffCurveEpsilon,
1220 rect.fLeft + rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001221#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001222 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001223 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001224 }
1225 }
1226 this->close();
1227}
1228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001230 assert_known_direction(dir);
1231
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001232 /* If addOval() is called after previous moveTo(),
1233 this path is still marked as an oval. This is used to
1234 fit into WebKit's calling sequences.
1235 We can't simply check isEmpty() in this case, as additional
1236 moveTo() would mark the path non empty.
1237 */
1238 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001239 if (fIsOval) {
1240 fDirection = dir;
1241 } else {
1242 fDirection = kUnknown_Direction;
1243 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001244
1245 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001246 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001247
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 SkAutoPathBoundsUpdate apbu(this, oval);
1249
1250 SkScalar cx = oval.centerX();
1251 SkScalar cy = oval.centerY();
1252 SkScalar rx = SkScalarHalf(oval.width());
1253 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1256 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1257 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1258 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1259
1260 /*
1261 To handle imprecision in computing the center and radii, we revert to
1262 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1263 to ensure that we don't exceed the oval's bounds *ever*, since we want
1264 to use oval for our fast-bounds, rather than have to recompute it.
1265 */
1266 const SkScalar L = oval.fLeft; // cx - rx
1267 const SkScalar T = oval.fTop; // cy - ry
1268 const SkScalar R = oval.fRight; // cx + rx
1269 const SkScalar B = oval.fBottom; // cy + ry
1270
1271 this->incReserve(17); // 8 quads + close
1272 this->moveTo(R, cy);
1273 if (dir == kCCW_Direction) {
1274 this->quadTo( R, cy - sy, cx + mx, cy - my);
1275 this->quadTo(cx + sx, T, cx , T);
1276 this->quadTo(cx - sx, T, cx - mx, cy - my);
1277 this->quadTo( L, cy - sy, L, cy );
1278 this->quadTo( L, cy + sy, cx - mx, cy + my);
1279 this->quadTo(cx - sx, B, cx , B);
1280 this->quadTo(cx + sx, B, cx + mx, cy + my);
1281 this->quadTo( R, cy + sy, R, cy );
1282 } else {
1283 this->quadTo( R, cy + sy, cx + mx, cy + my);
1284 this->quadTo(cx + sx, B, cx , B);
1285 this->quadTo(cx - sx, B, cx - mx, cy + my);
1286 this->quadTo( L, cy + sy, L, cy );
1287 this->quadTo( L, cy - sy, cx - mx, cy - my);
1288 this->quadTo(cx - sx, T, cx , T);
1289 this->quadTo(cx + sx, T, cx + mx, cy - my);
1290 this->quadTo( R, cy - sy, R, cy );
1291 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 this->close();
1293}
1294
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001295bool SkPath::isOval(SkRect* rect) const {
1296 if (fIsOval && rect) {
1297 *rect = getBounds();
1298 }
1299
1300 return fIsOval;
1301}
1302
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1304 if (r > 0) {
1305 SkRect rect;
1306 rect.set(x - r, y - r, x + r, y + r);
1307 this->addOval(rect, dir);
1308 }
1309}
1310
reed@android.com8a1c16f2008-12-17 15:59:43 +00001311void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1312 bool forceMoveTo) {
1313 if (oval.width() < 0 || oval.height() < 0) {
1314 return;
1315 }
1316
1317 SkPoint pts[kSkBuildQuadArcStorage];
1318 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1319 SkASSERT((count & 1) == 1);
1320
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001321 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 forceMoveTo = true;
1323 }
1324 this->incReserve(count);
1325 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1326 for (int i = 1; i < count; i += 2) {
1327 this->quadTo(pts[i], pts[i+1]);
1328 }
1329}
1330
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001331void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332 if (oval.isEmpty() || 0 == sweepAngle) {
1333 return;
1334 }
1335
1336 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1337
1338 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1339 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1340 return;
1341 }
1342
1343 SkPoint pts[kSkBuildQuadArcStorage];
1344 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1345
1346 this->incReserve(count);
1347 this->moveTo(pts[0]);
1348 for (int i = 1; i < count; i += 2) {
1349 this->quadTo(pts[i], pts[i+1]);
1350 }
1351}
1352
1353/*
1354 Need to handle the case when the angle is sharp, and our computed end-points
1355 for the arc go behind pt1 and/or p2...
1356*/
1357void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1358 SkScalar radius) {
1359 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001360
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361 // need to know our prev pt so we can construct tangent vectors
1362 {
1363 SkPoint start;
1364 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001365 // Handle degenerate cases by adding a line to the first point and
1366 // bailing out.
1367 if ((x1 == start.fX && y1 == start.fY) ||
1368 (x1 == x2 && y1 == y2) ||
1369 radius == 0) {
1370 this->lineTo(x1, y1);
1371 return;
1372 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 before.setNormalize(x1 - start.fX, y1 - start.fY);
1374 after.setNormalize(x2 - x1, y2 - y1);
1375 }
reed@google.comabf15c12011-01-18 20:35:51 +00001376
reed@android.com8a1c16f2008-12-17 15:59:43 +00001377 SkScalar cosh = SkPoint::DotProduct(before, after);
1378 SkScalar sinh = SkPoint::CrossProduct(before, after);
1379
1380 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001381 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 return;
1383 }
reed@google.comabf15c12011-01-18 20:35:51 +00001384
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1386 if (dist < 0) {
1387 dist = -dist;
1388 }
1389
1390 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1391 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1392 SkRotationDirection arcDir;
1393
1394 // now turn before/after into normals
1395 if (sinh > 0) {
1396 before.rotateCCW();
1397 after.rotateCCW();
1398 arcDir = kCW_SkRotationDirection;
1399 } else {
1400 before.rotateCW();
1401 after.rotateCW();
1402 arcDir = kCCW_SkRotationDirection;
1403 }
1404
1405 SkMatrix matrix;
1406 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001407
reed@android.com8a1c16f2008-12-17 15:59:43 +00001408 matrix.setScale(radius, radius);
1409 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1410 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001411
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001413
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 this->incReserve(count);
1415 // [xx,yy] == pts[0]
1416 this->lineTo(xx, yy);
1417 for (int i = 1; i < count; i += 2) {
1418 this->quadTo(pts[i], pts[i+1]);
1419 }
1420}
1421
1422///////////////////////////////////////////////////////////////////////////////
1423
1424void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1425 SkMatrix matrix;
1426
1427 matrix.setTranslate(dx, dy);
1428 this->addPath(path, matrix);
1429}
1430
1431void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001432 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001434 fIsOval = false;
1435
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001436 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 SkPoint pts[4];
1438 Verb verb;
1439
1440 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1441
1442 while ((verb = iter.next(pts)) != kDone_Verb) {
1443 switch (verb) {
1444 case kMove_Verb:
1445 proc(matrix, &pts[0], &pts[0], 1);
1446 this->moveTo(pts[0]);
1447 break;
1448 case kLine_Verb:
1449 proc(matrix, &pts[1], &pts[1], 1);
1450 this->lineTo(pts[1]);
1451 break;
1452 case kQuad_Verb:
1453 proc(matrix, &pts[1], &pts[1], 2);
1454 this->quadTo(pts[1], pts[2]);
1455 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001456 case kConic_Verb:
1457 proc(matrix, &pts[1], &pts[1], 2);
1458 this->conicTo(pts[1], pts[2], iter.conicWeight());
1459 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001460 case kCubic_Verb:
1461 proc(matrix, &pts[1], &pts[1], 3);
1462 this->cubicTo(pts[1], pts[2], pts[3]);
1463 break;
1464 case kClose_Verb:
1465 this->close();
1466 break;
1467 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001468 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 }
1470 }
1471}
1472
1473///////////////////////////////////////////////////////////////////////////////
1474
reed@google.com277c3f82013-05-31 15:17:50 +00001475static int pts_in_verb(unsigned verb) {
1476 static const uint8_t gPtsInVerb[] = {
1477 1, // kMove
1478 1, // kLine
1479 2, // kQuad
1480 2, // kConic
1481 3, // kCubic
1482 0, // kClose
1483 0 // kDone
1484 };
1485
1486 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1487 return gPtsInVerb[verb];
1488}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489
1490// ignore the initial moveto, and stop when the 1st contour ends
1491void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001492 int i, vcount = path.fPathRef->countVerbs();
1493 // exit early if the path is empty, or just has a moveTo.
1494 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 return;
1496 }
1497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001498 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001500 fIsOval = false;
1501
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001502 const uint8_t* verbs = path.fPathRef->verbs();
1503 // skip the initial moveTo
1504 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001505 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001509 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 case kLine_Verb:
1511 this->lineTo(pts[0].fX, pts[0].fY);
1512 break;
1513 case kQuad_Verb:
1514 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1515 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001516 case kConic_Verb:
1517 this->conicTo(pts[0], pts[1], *conicWeight++);
1518 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001520 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 +00001521 break;
1522 case kClose_Verb:
1523 return;
1524 }
reed@google.com277c3f82013-05-31 15:17:50 +00001525 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 }
1527}
1528
1529// ignore the last point of the 1st contour
1530void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 int i, vcount = path.fPathRef->countVerbs();
1532 // exit early if the path is empty, or just has a moveTo.
1533 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 return;
1535 }
1536
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001539 fIsOval = false;
1540
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001541 const uint8_t* verbs = path.fPathRef->verbs();
1542 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001543 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001545 SkASSERT(verbs[~0] == kMove_Verb);
1546 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001547 unsigned v = verbs[~i];
1548 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 if (n == 0) {
1550 break;
1551 }
1552 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001553 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 }
1555
1556 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001557 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 case kLine_Verb:
1559 this->lineTo(pts[-1].fX, pts[-1].fY);
1560 break;
1561 case kQuad_Verb:
1562 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1563 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001564 case kConic_Verb:
1565 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1566 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 case kCubic_Verb:
1568 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1569 pts[-3].fX, pts[-3].fY);
1570 break;
1571 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001572 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 break;
1574 }
reed@google.com277c3f82013-05-31 15:17:50 +00001575 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 }
1577}
1578
reed@google.com63d73742012-01-10 15:33:12 +00001579void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001580 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001581
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001582 const SkPoint* pts = src.fPathRef->pointsEnd();
1583 // we will iterator through src's verbs backwards
1584 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1585 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001586 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001587
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001588 fIsOval = false;
1589
reed@google.com63d73742012-01-10 15:33:12 +00001590 bool needMove = true;
1591 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001592 while (verbs < verbsEnd) {
1593 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001594 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001595
1596 if (needMove) {
1597 --pts;
1598 this->moveTo(pts->fX, pts->fY);
1599 needMove = false;
1600 }
1601 pts -= n;
1602 switch (v) {
1603 case kMove_Verb:
1604 if (needClose) {
1605 this->close();
1606 needClose = false;
1607 }
1608 needMove = true;
1609 pts += 1; // so we see the point in "if (needMove)" above
1610 break;
1611 case kLine_Verb:
1612 this->lineTo(pts[0]);
1613 break;
1614 case kQuad_Verb:
1615 this->quadTo(pts[1], pts[0]);
1616 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001617 case kConic_Verb:
1618 this->conicTo(pts[1], pts[0], *--conicWeights);
1619 break;
reed@google.com63d73742012-01-10 15:33:12 +00001620 case kCubic_Verb:
1621 this->cubicTo(pts[2], pts[1], pts[0]);
1622 break;
1623 case kClose_Verb:
1624 needClose = true;
1625 break;
1626 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001627 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001628 }
1629 }
1630}
1631
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632///////////////////////////////////////////////////////////////////////////////
1633
1634void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1635 SkMatrix matrix;
1636
1637 matrix.setTranslate(dx, dy);
1638 this->transform(matrix, dst);
1639}
1640
1641#include "SkGeometry.h"
1642
1643static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1644 int level = 2) {
1645 if (--level >= 0) {
1646 SkPoint tmp[5];
1647
1648 SkChopQuadAtHalf(pts, tmp);
1649 subdivide_quad_to(path, &tmp[0], level);
1650 subdivide_quad_to(path, &tmp[2], level);
1651 } else {
1652 path->quadTo(pts[1], pts[2]);
1653 }
1654}
1655
1656static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1657 int level = 2) {
1658 if (--level >= 0) {
1659 SkPoint tmp[7];
1660
1661 SkChopCubicAtHalf(pts, tmp);
1662 subdivide_cubic_to(path, &tmp[0], level);
1663 subdivide_cubic_to(path, &tmp[3], level);
1664 } else {
1665 path->cubicTo(pts[1], pts[2], pts[3]);
1666 }
1667}
1668
1669void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1670 SkDEBUGCODE(this->validate();)
1671 if (dst == NULL) {
1672 dst = (SkPath*)this;
1673 }
1674
tomhudson@google.com8d430182011-06-06 19:11:19 +00001675 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 SkPath tmp;
1677 tmp.fFillType = fFillType;
1678
1679 SkPath::Iter iter(*this, false);
1680 SkPoint pts[4];
1681 SkPath::Verb verb;
1682
reed@google.com4a3b7142012-05-16 17:16:46 +00001683 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 switch (verb) {
1685 case kMove_Verb:
1686 tmp.moveTo(pts[0]);
1687 break;
1688 case kLine_Verb:
1689 tmp.lineTo(pts[1]);
1690 break;
1691 case kQuad_Verb:
1692 subdivide_quad_to(&tmp, pts);
1693 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001694 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001695 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001696 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1697 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 case kCubic_Verb:
1699 subdivide_cubic_to(&tmp, pts);
1700 break;
1701 case kClose_Verb:
1702 tmp.close();
1703 break;
1704 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001705 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 break;
1707 }
1708 }
1709
1710 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001711 SkPathRef::Editor ed(&dst->fPathRef);
1712 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001713 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001715 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1716
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001719 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001720 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001722
robertphillips@google.com7101abe2013-10-29 22:45:37 +00001723#ifdef SK_BUILD_FOR_ANDROID
1724 if (!matrix.isIdentity() && !dst->hasComputedBounds()) {
1725 GEN_ID_PTR_INC(dst);
1726 }
1727#endif
1728
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001729 if (kUnknown_Direction == fDirection) {
1730 dst->fDirection = kUnknown_Direction;
1731 } else {
1732 SkScalar det2x2 =
1733 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1734 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1735 if (det2x2 < 0) {
1736 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1737 } else if (det2x2 > 0) {
1738 dst->fDirection = fDirection;
1739 } else {
1740 dst->fDirection = kUnknown_Direction;
1741 }
1742 }
1743
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001744 // It's an oval only if it stays a rect.
1745 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001746
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 SkDEBUGCODE(dst->validate();)
1748 }
1749}
1750
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751///////////////////////////////////////////////////////////////////////////////
1752///////////////////////////////////////////////////////////////////////////////
1753
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001754enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001755 kEmptyContour_SegmentState, // The current contour is empty. We may be
1756 // starting processing or we may have just
1757 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001758 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1759 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1760 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761};
1762
1763SkPath::Iter::Iter() {
1764#ifdef SK_DEBUG
1765 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001766 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001768 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001769 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770#endif
1771 // need to init enough to make next() harmlessly return kDone_Verb
1772 fVerbs = NULL;
1773 fVerbStop = NULL;
1774 fNeedClose = false;
1775}
1776
1777SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1778 this->setPath(path, forceClose);
1779}
1780
1781void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001782 fPts = path.fPathRef->points();
1783 fVerbs = path.fPathRef->verbs();
1784 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001785 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001786 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001787 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 fForceClose = SkToU8(forceClose);
1789 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001790 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791}
1792
1793bool SkPath::Iter::isClosedContour() const {
1794 if (fVerbs == NULL || fVerbs == fVerbStop) {
1795 return false;
1796 }
1797 if (fForceClose) {
1798 return true;
1799 }
1800
1801 const uint8_t* verbs = fVerbs;
1802 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001803
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001804 if (kMove_Verb == *(verbs - 1)) {
1805 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806 }
1807
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 while (verbs > stop) {
1809 // verbs points one beyond the current verb, decrement first.
1810 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811 if (kMove_Verb == v) {
1812 break;
1813 }
1814 if (kClose_Verb == v) {
1815 return true;
1816 }
1817 }
1818 return false;
1819}
1820
1821SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001822 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001824 // A special case: if both points are NaN, SkPoint::operation== returns
1825 // false, but the iterator expects that they are treated as the same.
1826 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001827 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1828 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001829 return kClose_Verb;
1830 }
1831
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832 pts[0] = fLastPt;
1833 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 fLastPt = fMoveTo;
1835 fCloseLine = true;
1836 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001837 } else {
1838 pts[0] = fMoveTo;
1839 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841}
1842
reed@google.com9e25dbf2012-05-15 17:05:38 +00001843const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 if (fSegmentState == kAfterMove_SegmentState) {
1845 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001847 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1850 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001851 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853}
1854
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855void SkPath::Iter::consumeDegenerateSegments() {
1856 // We need to step over anything that will not move the current draw point
1857 // forward before the next move is seen
1858 const uint8_t* lastMoveVerb = 0;
1859 const SkPoint* lastMovePt = 0;
1860 SkPoint lastPt = fLastPt;
1861 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001862 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001863 switch (verb) {
1864 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 // Keep a record of this most recent move
1866 lastMoveVerb = fVerbs;
1867 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001868 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001869 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 fPts++;
1871 break;
1872
1873 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001874 // A close when we are in a segment is always valid except when it
1875 // follows a move which follows a segment.
1876 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 return;
1878 }
1879 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001880 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001881 break;
1882
1883 case kLine_Verb:
1884 if (!IsLineDegenerate(lastPt, fPts[0])) {
1885 if (lastMoveVerb) {
1886 fVerbs = lastMoveVerb;
1887 fPts = lastMovePt;
1888 return;
1889 }
1890 return;
1891 }
1892 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001893 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 fPts++;
1895 break;
1896
reed@google.com277c3f82013-05-31 15:17:50 +00001897 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 case kQuad_Verb:
1899 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1900 if (lastMoveVerb) {
1901 fVerbs = lastMoveVerb;
1902 fPts = lastMovePt;
1903 return;
1904 }
1905 return;
1906 }
1907 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001908 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001910 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 break;
1912
1913 case kCubic_Verb:
1914 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1915 if (lastMoveVerb) {
1916 fVerbs = lastMoveVerb;
1917 fPts = lastMovePt;
1918 return;
1919 }
1920 return;
1921 }
1922 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001923 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 fPts += 3;
1925 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001926
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001928 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 }
1930 }
1931}
1932
reed@google.com4a3b7142012-05-16 17:16:46 +00001933SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001934 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 // Close the curve if requested and if there is some curve to close
1938 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001939 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001940 return kLine_Verb;
1941 }
1942 fNeedClose = false;
1943 return kClose_Verb;
1944 }
1945 return kDone_Verb;
1946 }
1947
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001948 // fVerbs is one beyond the current verb, decrement first
1949 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001950 const SkPoint* SK_RESTRICT srcPts = fPts;
1951 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952
1953 switch (verb) {
1954 case kMove_Verb:
1955 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001956 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 verb = this->autoClose(pts);
1958 if (verb == kClose_Verb) {
1959 fNeedClose = false;
1960 }
1961 return (Verb)verb;
1962 }
1963 if (fVerbs == fVerbStop) { // might be a trailing moveto
1964 return kDone_Verb;
1965 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001966 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001967 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001969 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001971 fNeedClose = fForceClose;
1972 break;
1973 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001974 pts[0] = this->cons_moveTo();
1975 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001976 fLastPt = srcPts[0];
1977 fCloseLine = false;
1978 srcPts += 1;
1979 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001980 case kConic_Verb:
1981 fConicWeights += 1;
1982 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001984 pts[0] = this->cons_moveTo();
1985 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 fLastPt = srcPts[1];
1987 srcPts += 2;
1988 break;
1989 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001990 pts[0] = this->cons_moveTo();
1991 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 fLastPt = srcPts[2];
1993 srcPts += 3;
1994 break;
1995 case kClose_Verb:
1996 verb = this->autoClose(pts);
1997 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001998 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 } else {
2000 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002001 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004 break;
2005 }
2006 fPts = srcPts;
2007 return (Verb)verb;
2008}
2009
2010///////////////////////////////////////////////////////////////////////////////
2011
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002012SkPath::RawIter::RawIter() {
2013#ifdef SK_DEBUG
2014 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002015 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002016 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2017#endif
2018 // need to init enough to make next() harmlessly return kDone_Verb
2019 fVerbs = NULL;
2020 fVerbStop = NULL;
2021}
2022
2023SkPath::RawIter::RawIter(const SkPath& path) {
2024 this->setPath(path);
2025}
2026
2027void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002028 fPts = path.fPathRef->points();
2029 fVerbs = path.fPathRef->verbs();
2030 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002031 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002032 fMoveTo.fX = fMoveTo.fY = 0;
2033 fLastPt.fX = fLastPt.fY = 0;
2034}
2035
2036SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002037 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002038 if (fVerbs == fVerbStop) {
2039 return kDone_Verb;
2040 }
2041
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002042 // fVerbs points one beyond next verb so decrement first.
2043 unsigned verb = *(--fVerbs);
2044 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002045
2046 switch (verb) {
2047 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002048 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002049 fMoveTo = srcPts[0];
2050 fLastPt = fMoveTo;
2051 srcPts += 1;
2052 break;
2053 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002054 pts[0] = fLastPt;
2055 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002056 fLastPt = srcPts[0];
2057 srcPts += 1;
2058 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002059 case kConic_Verb:
2060 fConicWeights += 1;
2061 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002062 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002063 pts[0] = fLastPt;
2064 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002065 fLastPt = srcPts[1];
2066 srcPts += 2;
2067 break;
2068 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002069 pts[0] = fLastPt;
2070 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002071 fLastPt = srcPts[2];
2072 srcPts += 3;
2073 break;
2074 case kClose_Verb:
2075 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002076 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002077 break;
2078 }
2079 fPts = srcPts;
2080 return (Verb)verb;
2081}
2082
2083///////////////////////////////////////////////////////////////////////////////
2084
reed@android.com8a1c16f2008-12-17 15:59:43 +00002085/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002086 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087*/
2088
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002089uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090 SkDEBUGCODE(this->validate();)
2091
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002092 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002093 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002094 return SkAlign4(byteCount);
2095 }
2096
2097 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002098
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002099 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002100 (fConvexity << kConvexity_SerializationShift) |
2101 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002102 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002103 (fDirection << kDirection_SerializationShift)
2104#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2105 | (0x1 << kNewFormat_SerializationShift);
2106#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002107
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002108 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002109
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002110 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002111
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002112 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002113 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002114}
2115
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002116uint32_t SkPath::readFromMemory(const void* storage) {
2117 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002118
reed@google.com98b11f12011-09-21 18:40:27 +00002119 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002120 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2121 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2122 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002123 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002124 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002125#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2126 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2127#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002128
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002129 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2130#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2131 , newFormat, packed)
2132#endif
2133 );
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002134
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002135 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002136
robertphillips@google.com7101abe2013-10-29 22:45:37 +00002137 GEN_ID_INC;
2138
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002140 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141}
2142
2143///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144
reed@google.com51bbe752013-01-17 22:07:50 +00002145#include "SkString.h"
2146
2147static void append_scalar(SkString* str, SkScalar value) {
2148 SkString tmp;
2149 tmp.printf("%g", value);
2150 if (tmp.contains('.')) {
2151 tmp.appendUnichar('f');
2152 }
2153 str->append(tmp);
2154}
2155
2156static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002157 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002158 str->append(label);
2159 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002160
reed@google.com51bbe752013-01-17 22:07:50 +00002161 const SkScalar* values = &pts[0].fX;
2162 count *= 2;
2163
2164 for (int i = 0; i < count; ++i) {
2165 append_scalar(str, values[i]);
2166 if (i < count - 1) {
2167 str->append(", ");
2168 }
2169 }
reed@google.com277c3f82013-05-31 15:17:50 +00002170 if (conicWeight >= 0) {
2171 str->append(", ");
2172 append_scalar(str, conicWeight);
2173 }
reed@google.com51bbe752013-01-17 22:07:50 +00002174 str->append(");\n");
2175}
2176
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177void SkPath::dump(bool forceClose, const char title[]) const {
2178 Iter iter(*this, forceClose);
2179 SkPoint pts[4];
2180 Verb verb;
2181
2182 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2183 title ? title : "");
2184
reed@google.com51bbe752013-01-17 22:07:50 +00002185 SkString builder;
2186
reed@google.com4a3b7142012-05-16 17:16:46 +00002187 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188 switch (verb) {
2189 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002190 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002191 break;
2192 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002193 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194 break;
2195 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002196 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002198 case kConic_Verb:
2199 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2200 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002201 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002202 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002203 break;
2204 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002205 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002206 break;
2207 default:
2208 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2209 verb = kDone_Verb; // stop the loop
2210 break;
2211 }
2212 }
reed@google.com51bbe752013-01-17 22:07:50 +00002213 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002214}
2215
reed@android.come522ca52009-11-23 20:10:41 +00002216void SkPath::dump() const {
2217 this->dump(false);
2218}
2219
2220#ifdef SK_DEBUG
2221void SkPath::validate() const {
2222 SkASSERT(this != NULL);
2223 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002224
djsollen@google.com077348c2012-10-22 20:23:32 +00002225#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002226 if (!fBoundsIsDirty) {
2227 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002228
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002229 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002230 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002231
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002232 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002233 // if we're empty, fBounds may be empty but translated, so we can't
2234 // necessarily compare to bounds directly
2235 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2236 // be [2, 2, 2, 2]
2237 SkASSERT(bounds.isEmpty());
2238 SkASSERT(fBounds.isEmpty());
2239 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002240 if (bounds.isEmpty()) {
2241 SkASSERT(fBounds.isEmpty());
2242 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002243 if (!fBounds.isEmpty()) {
2244 SkASSERT(fBounds.contains(bounds));
2245 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002246 }
reed@android.come522ca52009-11-23 20:10:41 +00002247 }
2248 }
reed@google.com10296cc2011-09-21 12:29:05 +00002249
2250 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002251 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2252 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2253 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002254 case kLine_Verb:
2255 mask |= kLine_SegmentMask;
2256 break;
2257 case kQuad_Verb:
2258 mask |= kQuad_SegmentMask;
2259 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002260 case kConic_Verb:
2261 mask |= kConic_SegmentMask;
2262 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002263 case kCubic_Verb:
2264 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002265 case kMove_Verb: // these verbs aren't included in the segment mask.
2266 case kClose_Verb:
2267 break;
2268 case kDone_Verb:
2269 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2270 break;
2271 default:
2272 SkDEBUGFAIL("Unknown Verb");
2273 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002274 }
2275 }
2276 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002277#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002278}
djsollen@google.com077348c2012-10-22 20:23:32 +00002279#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002280
reed@google.com04863fa2011-05-15 04:08:24 +00002281///////////////////////////////////////////////////////////////////////////////
2282
reed@google.com0b7b9822011-05-16 12:29:27 +00002283static int sign(SkScalar x) { return x < 0; }
2284#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002285
reed@google.com04863fa2011-05-15 04:08:24 +00002286static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002287 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002288}
2289
2290// only valid for a single contour
2291struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002292 Convexicator()
2293 : fPtCount(0)
2294 , fConvexity(SkPath::kConvex_Convexity)
2295 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002296 fSign = 0;
2297 // warnings
2298 fCurrPt.set(0, 0);
2299 fVec0.set(0, 0);
2300 fVec1.set(0, 0);
2301 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002302
2303 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002304 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002305 }
2306
2307 SkPath::Convexity getConvexity() const { return fConvexity; }
2308
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002309 /** The direction returned is only valid if the path is determined convex */
2310 SkPath::Direction getDirection() const { return fDirection; }
2311
reed@google.com04863fa2011-05-15 04:08:24 +00002312 void addPt(const SkPoint& pt) {
2313 if (SkPath::kConcave_Convexity == fConvexity) {
2314 return;
2315 }
2316
2317 if (0 == fPtCount) {
2318 fCurrPt = pt;
2319 ++fPtCount;
2320 } else {
2321 SkVector vec = pt - fCurrPt;
2322 if (vec.fX || vec.fY) {
2323 fCurrPt = pt;
2324 if (++fPtCount == 2) {
2325 fFirstVec = fVec1 = vec;
2326 } else {
2327 SkASSERT(fPtCount > 2);
2328 this->addVec(vec);
2329 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002330
reed@google.com85b6e392011-05-15 20:25:17 +00002331 int sx = sign(vec.fX);
2332 int sy = sign(vec.fY);
2333 fDx += (sx != fSx);
2334 fDy += (sy != fSy);
2335 fSx = sx;
2336 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002337
reed@google.com85b6e392011-05-15 20:25:17 +00002338 if (fDx > 3 || fDy > 3) {
2339 fConvexity = SkPath::kConcave_Convexity;
2340 }
reed@google.com04863fa2011-05-15 04:08:24 +00002341 }
2342 }
2343 }
2344
2345 void close() {
2346 if (fPtCount > 2) {
2347 this->addVec(fFirstVec);
2348 }
2349 }
2350
2351private:
2352 void addVec(const SkVector& vec) {
2353 SkASSERT(vec.fX || vec.fY);
2354 fVec0 = fVec1;
2355 fVec1 = vec;
2356 int sign = CrossProductSign(fVec0, fVec1);
2357 if (0 == fSign) {
2358 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002359 if (1 == sign) {
2360 fDirection = SkPath::kCW_Direction;
2361 } else if (-1 == sign) {
2362 fDirection = SkPath::kCCW_Direction;
2363 }
reed@google.com04863fa2011-05-15 04:08:24 +00002364 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002365 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002366 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002367 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002368 }
2369 }
2370 }
2371
2372 SkPoint fCurrPt;
2373 SkVector fVec0, fVec1, fFirstVec;
2374 int fPtCount; // non-degenerate points
2375 int fSign;
2376 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002377 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002378 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002379};
2380
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002381SkPath::Convexity SkPath::internalGetConvexity() const {
2382 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002383 SkPoint pts[4];
2384 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002385 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002386
2387 int contourCount = 0;
2388 int count;
2389 Convexicator state;
2390
2391 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2392 switch (verb) {
2393 case kMove_Verb:
2394 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002395 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002396 return kConcave_Convexity;
2397 }
2398 pts[1] = pts[0];
2399 count = 1;
2400 break;
2401 case kLine_Verb: count = 1; break;
2402 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002403 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 case kCubic_Verb: count = 3; break;
2405 case kClose_Verb:
2406 state.close();
2407 count = 0;
2408 break;
2409 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002410 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002411 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002412 return kConcave_Convexity;
2413 }
2414
2415 for (int i = 1; i <= count; i++) {
2416 state.addPt(pts[i]);
2417 }
2418 // early exit
2419 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002420 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002421 return kConcave_Convexity;
2422 }
2423 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002424 fConvexity = state.getConvexity();
2425 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2426 fDirection = state.getDirection();
2427 }
2428 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002429}
reed@google.com69a99432012-01-10 18:00:10 +00002430
2431///////////////////////////////////////////////////////////////////////////////
2432
2433class ContourIter {
2434public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002435 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002436
2437 bool done() const { return fDone; }
2438 // if !done() then these may be called
2439 int count() const { return fCurrPtCount; }
2440 const SkPoint* pts() const { return fCurrPt; }
2441 void next();
2442
2443private:
2444 int fCurrPtCount;
2445 const SkPoint* fCurrPt;
2446 const uint8_t* fCurrVerb;
2447 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002448 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002449 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002450 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002451};
2452
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002453ContourIter::ContourIter(const SkPathRef& pathRef) {
2454 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002455 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002456 fCurrPt = pathRef.points();
2457 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002458 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002459 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002460 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002461 this->next();
2462}
2463
2464void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002465 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002466 fDone = true;
2467 }
2468 if (fDone) {
2469 return;
2470 }
2471
2472 // skip pts of prev contour
2473 fCurrPt += fCurrPtCount;
2474
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002475 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002476 int ptCount = 1; // moveTo
2477 const uint8_t* verbs = fCurrVerb;
2478
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002479 for (--verbs; verbs > fStopVerbs; --verbs) {
2480 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002481 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002482 goto CONTOUR_END;
2483 case SkPath::kLine_Verb:
2484 ptCount += 1;
2485 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002486 case SkPath::kConic_Verb:
2487 fCurrConicWeight += 1;
2488 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002489 case SkPath::kQuad_Verb:
2490 ptCount += 2;
2491 break;
2492 case SkPath::kCubic_Verb:
2493 ptCount += 3;
2494 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002495 case SkPath::kClose_Verb:
2496 break;
2497 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002498 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002499 break;
2500 }
2501 }
2502CONTOUR_END:
2503 fCurrPtCount = ptCount;
2504 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002505 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002506}
2507
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002508// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002509static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002510 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2511 // We may get 0 when the above subtracts underflow. We expect this to be
2512 // very rare and lazily promote to double.
2513 if (0 == cross) {
2514 double p0x = SkScalarToDouble(p0.fX);
2515 double p0y = SkScalarToDouble(p0.fY);
2516
2517 double p1x = SkScalarToDouble(p1.fX);
2518 double p1y = SkScalarToDouble(p1.fY);
2519
2520 double p2x = SkScalarToDouble(p2.fX);
2521 double p2y = SkScalarToDouble(p2.fY);
2522
2523 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2524 (p1y - p0y) * (p2x - p0x));
2525
2526 }
2527 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002528}
2529
reed@google.comc1ea60a2012-01-31 15:15:36 +00002530// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002531static int find_max_y(const SkPoint pts[], int count) {
2532 SkASSERT(count > 0);
2533 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002534 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002535 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002536 SkScalar y = pts[i].fY;
2537 if (y > max) {
2538 max = y;
2539 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002540 }
2541 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002542 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002543}
2544
reed@google.comcabaf1d2012-01-11 21:03:05 +00002545static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2546 int i = index;
2547 for (;;) {
2548 i = (i + inc) % n;
2549 if (i == index) { // we wrapped around, so abort
2550 break;
2551 }
2552 if (pts[index] != pts[i]) { // found a different point, success!
2553 break;
2554 }
2555 }
2556 return i;
2557}
2558
reed@google.comc1ea60a2012-01-31 15:15:36 +00002559/**
2560 * Starting at index, and moving forward (incrementing), find the xmin and
2561 * xmax of the contiguous points that have the same Y.
2562 */
2563static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2564 int* maxIndexPtr) {
2565 const SkScalar y = pts[index].fY;
2566 SkScalar min = pts[index].fX;
2567 SkScalar max = min;
2568 int minIndex = index;
2569 int maxIndex = index;
2570 for (int i = index + 1; i < n; ++i) {
2571 if (pts[i].fY != y) {
2572 break;
2573 }
2574 SkScalar x = pts[i].fX;
2575 if (x < min) {
2576 min = x;
2577 minIndex = i;
2578 } else if (x > max) {
2579 max = x;
2580 maxIndex = i;
2581 }
2582 }
2583 *maxIndexPtr = maxIndex;
2584 return minIndex;
2585}
2586
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002587static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002588 if (dir) {
2589 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2590 }
reed@google.comac8543f2012-01-30 20:51:25 +00002591}
2592
reed@google.comc1ea60a2012-01-31 15:15:36 +00002593#if 0
2594#include "SkString.h"
2595#include "../utils/SkParsePath.h"
2596static void dumpPath(const SkPath& path) {
2597 SkString str;
2598 SkParsePath::ToSVGString(path, &str);
2599 SkDebugf("%s\n", str.c_str());
2600}
2601#endif
2602
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002603namespace {
2604// for use with convex_dir_test
2605double mul(double a, double b) { return a * b; }
2606SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2607double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2608SkScalar toScalar(SkScalar a) { return a; }
2609
2610// determines the winding direction of a convex polygon with the precision
2611// of T. CAST_SCALAR casts an SkScalar to T.
2612template <typename T, T (CAST_SCALAR)(SkScalar)>
2613bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2614 // we find the first three points that form a non-degenerate
2615 // triangle. If there are no such points then the path is
2616 // degenerate. The first is always point 0. Now we find the second
2617 // point.
2618 int i = 0;
2619 enum { kX = 0, kY = 1 };
2620 T v0[2];
2621 while (1) {
2622 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2623 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2624 if (v0[kX] || v0[kY]) {
2625 break;
2626 }
2627 if (++i == n - 1) {
2628 return false;
2629 }
2630 }
2631 // now find a third point that is not colinear with the first two
2632 // points and check the orientation of the triangle (which will be
2633 // the same as the orientation of the path).
2634 for (++i; i < n; ++i) {
2635 T v1[2];
2636 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2637 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2638 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2639 if (0 != cross) {
2640 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2641 return true;
2642 }
2643 }
2644 return false;
2645}
2646}
2647
reed@google.comac8543f2012-01-30 20:51:25 +00002648/*
2649 * We loop through all contours, and keep the computed cross-product of the
2650 * contour that contained the global y-max. If we just look at the first
2651 * contour, we may find one that is wound the opposite way (correctly) since
2652 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2653 * that is outer most (or at least has the global y-max) before we can consider
2654 * its cross product.
2655 */
reed@google.com69a99432012-01-10 18:00:10 +00002656bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002657// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002658 // don't want to pay the cost for computing this if it
2659 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002660
2661 if (kUnknown_Direction != fDirection) {
2662 *dir = static_cast<Direction>(fDirection);
2663 return true;
2664 }
reed@google.com69a99432012-01-10 18:00:10 +00002665 const Convexity conv = this->getConvexityOrUnknown();
2666
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002667 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002668
reed@google.comac8543f2012-01-30 20:51:25 +00002669 // initialize with our logical y-min
2670 SkScalar ymax = this->getBounds().fTop;
2671 SkScalar ymaxCross = 0;
2672
reed@google.com69a99432012-01-10 18:00:10 +00002673 for (; !iter.done(); iter.next()) {
2674 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002675 if (n < 3) {
2676 continue;
2677 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002678
reed@google.comcabaf1d2012-01-11 21:03:05 +00002679 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002680 SkScalar cross = 0;
2681 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002682 // We try first at scalar precision, and then again at double
2683 // precision. This is because the vectors computed between distant
2684 // points may lose too much precision.
2685 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002686 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002687 return true;
2688 }
2689 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002690 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002691 return true;
2692 } else {
2693 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002694 }
2695 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002696 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002697 if (pts[index].fY < ymax) {
2698 continue;
2699 }
2700
reed@google.comc1ea60a2012-01-31 15:15:36 +00002701 // If there is more than 1 distinct point at the y-max, we take the
2702 // x-min and x-max of them and just subtract to compute the dir.
2703 if (pts[(index + 1) % n].fY == pts[index].fY) {
2704 int maxIndex;
2705 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002706 if (minIndex == maxIndex) {
2707 goto TRY_CROSSPROD;
2708 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002709 SkASSERT(pts[minIndex].fY == pts[index].fY);
2710 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2711 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2712 // we just subtract the indices, and let that auto-convert to
2713 // SkScalar, since we just want - or + to signal the direction.
2714 cross = minIndex - maxIndex;
2715 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002716 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002717 // Find a next and prev index to use for the cross-product test,
2718 // but we try to find pts that form non-zero vectors from pts[index]
2719 //
2720 // Its possible that we can't find two non-degenerate vectors, so
2721 // we have to guard our search (e.g. all the pts could be in the
2722 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002723
reed@google.comc1ea60a2012-01-31 15:15:36 +00002724 // we pass n - 1 instead of -1 so we don't foul up % operator by
2725 // passing it a negative LH argument.
2726 int prev = find_diff_pt(pts, index, n, n - 1);
2727 if (prev == index) {
2728 // completely degenerate, skip to next contour
2729 continue;
2730 }
2731 int next = find_diff_pt(pts, index, n, 1);
2732 SkASSERT(next != index);
2733 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002734 // 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 +00002735 // x-direction. We really should continue to walk away from the degeneracy until
2736 // there is a divergence.
2737 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002738 // construct the subtract so we get the correct Direction below
2739 cross = pts[index].fX - pts[next].fX;
2740 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002741 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002742
reed@google.comac8543f2012-01-30 20:51:25 +00002743 if (cross) {
2744 // record our best guess so far
2745 ymax = pts[index].fY;
2746 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002747 }
reed@google.com69a99432012-01-10 18:00:10 +00002748 }
2749 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002750 if (ymaxCross) {
2751 crossToDir(ymaxCross, dir);
2752 fDirection = *dir;
2753 return true;
2754 } else {
2755 return false;
2756 }
reed@google.comac8543f2012-01-30 20:51:25 +00002757}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758
2759///////////////////////////////////////////////////////////////////////////////
2760
2761static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2762 SkScalar D, SkScalar t) {
2763 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2764}
2765
2766static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2767 SkScalar t) {
2768 SkScalar A = c3 + 3*(c1 - c2) - c0;
2769 SkScalar B = 3*(c2 - c1 - c1 + c0);
2770 SkScalar C = 3*(c1 - c0);
2771 SkScalar D = c0;
2772 return eval_cubic_coeff(A, B, C, D, t);
2773}
2774
2775/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2776 t value such that cubic(t) = target
2777 */
2778static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2779 SkScalar target, SkScalar* t) {
2780 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2781 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002782
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783 SkScalar D = c0 - target;
2784 SkScalar A = c3 + 3*(c1 - c2) - c0;
2785 SkScalar B = 3*(c2 - c1 - c1 + c0);
2786 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002787
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2789 SkScalar minT = 0;
2790 SkScalar maxT = SK_Scalar1;
2791 SkScalar mid;
2792 int i;
2793 for (i = 0; i < 16; i++) {
2794 mid = SkScalarAve(minT, maxT);
2795 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2796 if (delta < 0) {
2797 minT = mid;
2798 delta = -delta;
2799 } else {
2800 maxT = mid;
2801 }
2802 if (delta < TOLERANCE) {
2803 break;
2804 }
2805 }
2806 *t = mid;
2807 return true;
2808}
2809
2810template <size_t N> static void find_minmax(const SkPoint pts[],
2811 SkScalar* minPtr, SkScalar* maxPtr) {
2812 SkScalar min, max;
2813 min = max = pts[0].fX;
2814 for (size_t i = 1; i < N; ++i) {
2815 min = SkMinScalar(min, pts[i].fX);
2816 max = SkMaxScalar(max, pts[i].fX);
2817 }
2818 *minPtr = min;
2819 *maxPtr = max;
2820}
2821
2822static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2823 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002824
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 int dir = 1;
2826 if (pts[0].fY > pts[3].fY) {
2827 storage[0] = pts[3];
2828 storage[1] = pts[2];
2829 storage[2] = pts[1];
2830 storage[3] = pts[0];
2831 pts = storage;
2832 dir = -1;
2833 }
2834 if (y < pts[0].fY || y >= pts[3].fY) {
2835 return 0;
2836 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002837
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 // quickreject or quickaccept
2839 SkScalar min, max;
2840 find_minmax<4>(pts, &min, &max);
2841 if (x < min) {
2842 return 0;
2843 }
2844 if (x > max) {
2845 return dir;
2846 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002847
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 // compute the actual x(t) value
2849 SkScalar t, xt;
2850 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2851 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2852 } else {
2853 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2854 xt = y < mid ? pts[0].fX : pts[3].fX;
2855 }
2856 return xt < x ? dir : 0;
2857}
2858
2859static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2860 SkPoint dst[10];
2861 int n = SkChopCubicAtYExtrema(pts, dst);
2862 int w = 0;
2863 for (int i = 0; i <= n; ++i) {
2864 w += winding_mono_cubic(&dst[i * 3], x, y);
2865 }
2866 return w;
2867}
2868
2869static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2870 SkScalar y0 = pts[0].fY;
2871 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002872
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 int dir = 1;
2874 if (y0 > y2) {
2875 SkTSwap(y0, y2);
2876 dir = -1;
2877 }
2878 if (y < y0 || y >= y2) {
2879 return 0;
2880 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002881
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002882 // bounds check on X (not required. is it faster?)
2883#if 0
2884 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2885 return 0;
2886 }
2887#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002888
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889 SkScalar roots[2];
2890 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2891 2 * (pts[1].fY - pts[0].fY),
2892 pts[0].fY - y,
2893 roots);
2894 SkASSERT(n <= 1);
2895 SkScalar xt;
2896 if (0 == n) {
2897 SkScalar mid = SkScalarAve(y0, y2);
2898 // Need [0] and [2] if dir == 1
2899 // and [2] and [0] if dir == -1
2900 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2901 } else {
2902 SkScalar t = roots[0];
2903 SkScalar C = pts[0].fX;
2904 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2905 SkScalar B = 2 * (pts[1].fX - C);
2906 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2907 }
2908 return xt < x ? dir : 0;
2909}
2910
2911static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2912 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2913 if (y0 == y1) {
2914 return true;
2915 }
2916 if (y0 < y1) {
2917 return y1 <= y2;
2918 } else {
2919 return y1 >= y2;
2920 }
2921}
2922
2923static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2924 SkPoint dst[5];
2925 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002926
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002927 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2928 n = SkChopQuadAtYExtrema(pts, dst);
2929 pts = dst;
2930 }
2931 int w = winding_mono_quad(pts, x, y);
2932 if (n > 0) {
2933 w += winding_mono_quad(&pts[2], x, y);
2934 }
2935 return w;
2936}
2937
2938static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2939 SkScalar x0 = pts[0].fX;
2940 SkScalar y0 = pts[0].fY;
2941 SkScalar x1 = pts[1].fX;
2942 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002943
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002944 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002945
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002946 int dir = 1;
2947 if (y0 > y1) {
2948 SkTSwap(y0, y1);
2949 dir = -1;
2950 }
2951 if (y < y0 || y >= y1) {
2952 return 0;
2953 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002954
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2956 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002957
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002958 if (SkScalarSignAsInt(cross) == dir) {
2959 dir = 0;
2960 }
2961 return dir;
2962}
2963
2964bool SkPath::contains(SkScalar x, SkScalar y) const {
2965 bool isInverse = this->isInverseFillType();
2966 if (this->isEmpty()) {
2967 return isInverse;
2968 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002969
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002970 const SkRect& bounds = this->getBounds();
2971 if (!bounds.contains(x, y)) {
2972 return isInverse;
2973 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002974
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 SkPath::Iter iter(*this, true);
2976 bool done = false;
2977 int w = 0;
2978 do {
2979 SkPoint pts[4];
2980 switch (iter.next(pts, false)) {
2981 case SkPath::kMove_Verb:
2982 case SkPath::kClose_Verb:
2983 break;
2984 case SkPath::kLine_Verb:
2985 w += winding_line(pts, x, y);
2986 break;
2987 case SkPath::kQuad_Verb:
2988 w += winding_quad(pts, x, y);
2989 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002990 case SkPath::kConic_Verb:
2991 SkASSERT(0);
2992 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 case SkPath::kCubic_Verb:
2994 w += winding_cubic(pts, x, y);
2995 break;
2996 case SkPath::kDone_Verb:
2997 done = true;
2998 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002999 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003001
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 switch (this->getFillType()) {
3003 case SkPath::kEvenOdd_FillType:
3004 case SkPath::kInverseEvenOdd_FillType:
3005 w &= 1;
3006 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003007 default:
3008 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 }
3010 return SkToBool(w);
3011}