blob: 83f481a91b032dd441095cc97f0ae2dd2d7e78fd [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
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000150 , fSourcePath(NULL)
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;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000164
165 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
166 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000167}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000170 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000171 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000172#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000173 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000174#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176}
177
178SkPath::~SkPath() {
179 SkDEBUGCODE(this->validate();)
180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182SkPath& SkPath::operator=(const SkPath& that) {
183 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185 if (this != &that) {
186 fPathRef.reset(SkRef(that.fPathRef.get()));
187 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000188#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000189 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000190#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 }
192 SkDEBUGCODE(this->validate();)
193 return *this;
194}
195
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196void SkPath::copyFields(const SkPath& that) {
197 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 fLastMoveToIndex = that.fLastMoveToIndex;
199 fFillType = that.fFillType;
200 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 fConvexity = that.fConvexity;
202 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000203 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204}
205
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000206bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000207 // note: don't need to look at isConvex or bounds, since just comparing the
208 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000209
210 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
211 // since it is only a cache of info in the fVerbs, but its a fast way to
212 // notice a difference
213
reed@android.com3abec1d2009-03-02 05:36:20 +0000214 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000215 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000216 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000217}
218
bungeman@google.coma5809a32013-06-21 15:13:34 +0000219void SkPath::swap(SkPath& that) {
220 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000221
bungeman@google.coma5809a32013-06-21 15:13:34 +0000222 if (this != &that) {
223 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000224 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
225 SkTSwap<uint8_t>(fFillType, that.fFillType);
226 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000227 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
228 SkTSwap<uint8_t>(fDirection, that.fDirection);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000229 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000230#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000231 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
232#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000233 }
234}
235
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000236static inline bool check_edge_against_rect(const SkPoint& p0,
237 const SkPoint& p1,
238 const SkRect& rect,
239 SkPath::Direction dir) {
240 const SkPoint* edgeBegin;
241 SkVector v;
242 if (SkPath::kCW_Direction == dir) {
243 v = p1 - p0;
244 edgeBegin = &p0;
245 } else {
246 v = p0 - p1;
247 edgeBegin = &p1;
248 }
249 if (v.fX || v.fY) {
250 // check the cross product of v with the vec from edgeBegin to each rect corner
251 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
252 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
253 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
254 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
255 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
256 return false;
257 }
258 }
259 return true;
260}
261
262bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
263 // This only handles non-degenerate convex paths currently.
264 if (kConvex_Convexity != this->getConvexity()) {
265 return false;
266 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000267
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 Direction direction;
269 if (!this->cheapComputeDirection(&direction)) {
270 return false;
271 }
272
273 SkPoint firstPt;
274 SkPoint prevPt;
275 RawIter iter(*this);
276 SkPath::Verb verb;
277 SkPoint pts[4];
278 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000279 SkDEBUGCODE(int segmentCount = 0;)
280 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281
282 while ((verb = iter.next(pts)) != kDone_Verb) {
283 int nextPt = -1;
284 switch (verb) {
285 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000286 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 SkDEBUGCODE(++moveCnt);
288 firstPt = prevPt = pts[0];
289 break;
290 case kLine_Verb:
291 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000292 SkASSERT(moveCnt && !closeCount);
293 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 break;
295 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000296 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkASSERT(moveCnt && !closeCount);
298 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299 nextPt = 2;
300 break;
301 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
303 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 nextPt = 3;
305 break;
306 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000308 break;
309 default:
310 SkDEBUGFAIL("unknown verb");
311 }
312 if (-1 != nextPt) {
313 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
314 return false;
315 }
316 prevPt = pts[nextPt];
317 }
318 }
319
320 return check_edge_against_rect(prevPt, firstPt, rect, direction);
321}
322
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000323uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000324 uint32_t genID = fPathRef->genID();
325#ifdef SK_BUILD_FOR_ANDROID
326 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
327 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
328#endif
329 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000330}
djsollen@google.come63793a2012-03-21 15:39:03 +0000331
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000332#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000333const SkPath* SkPath::getSourcePath() const {
334 return fSourcePath;
335}
336
337void SkPath::setSourcePath(const SkPath* path) {
338 fSourcePath = path;
339}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000340#endif
341
reed@android.com8a1c16f2008-12-17 15:59:43 +0000342void SkPath::reset() {
343 SkDEBUGCODE(this->validate();)
344
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000345 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000346 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000347}
348
349void SkPath::rewind() {
350 SkDEBUGCODE(this->validate();)
351
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000352 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000353 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000354}
355
reed@google.com7e6c4d12012-05-10 14:05:43 +0000356bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000357 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000358
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000359 if (2 == verbCount) {
360 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
361 if (kLine_Verb == fPathRef->atVerb(1)) {
362 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000363 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000364 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000365 line[0] = pts[0];
366 line[1] = pts[1];
367 }
368 return true;
369 }
370 }
371 return false;
372}
373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374/*
375 Determines if path is a rect by keeping track of changes in direction
376 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378 The direction is computed such that:
379 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000380 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000381 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000382 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000383
caryclark@google.comf1316942011-07-26 19:54:45 +0000384A rectangle cycles up/right/down/left or up/left/down/right.
385
386The test fails if:
387 The path is closed, and followed by a line.
388 A second move creates a new endpoint.
389 A diagonal line is parsed.
390 There's more than four changes of direction.
391 There's a discontinuity on the line (e.g., a move in the middle)
392 The line reverses direction.
393 The rectangle doesn't complete a cycle.
394 The path contains a quadratic or cubic.
395 The path contains fewer than four points.
396 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000397
caryclark@google.comf1316942011-07-26 19:54:45 +0000398It's OK if the path has:
399 Several colinear line segments composing a rectangle side.
400 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000401
caryclark@google.comf1316942011-07-26 19:54:45 +0000402The direction takes advantage of the corners found since opposite sides
403must travel in opposite directions.
404
405FIXME: Allow colinear quads and cubics to be treated like lines.
406FIXME: If the API passes fill-only, return true if the filled stroke
407 is a rectangle, though the caller failed to close the path.
408 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000409bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
410 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000411 int corners = 0;
412 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000413 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000414 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000415 first.set(0, 0);
416 last.set(0, 0);
417 int firstDirection = 0;
418 int lastDirection = 0;
419 int nextDirection = 0;
420 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000421 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000422 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000423 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
424 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000425 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000426 savePts = pts;
427 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 autoClose = true;
429 case kLine_Verb: {
430 SkScalar left = last.fX;
431 SkScalar top = last.fY;
432 SkScalar right = pts->fX;
433 SkScalar bottom = pts->fY;
434 ++pts;
435 if (left != right && top != bottom) {
436 return false; // diagonal
437 }
438 if (left == right && top == bottom) {
439 break; // single point on side OK
440 }
441 nextDirection = (left != right) << 0 |
442 (left < right || top < bottom) << 1;
443 if (0 == corners) {
444 firstDirection = nextDirection;
445 first = last;
446 last = pts[-1];
447 corners = 1;
448 closedOrMoved = false;
449 break;
450 }
451 if (closedOrMoved) {
452 return false; // closed followed by a line
453 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000454 if (autoClose && nextDirection == firstDirection) {
455 break; // colinear with first
456 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000457 closedOrMoved = autoClose;
458 if (lastDirection != nextDirection) {
459 if (++corners > 4) {
460 return false; // too many direction changes
461 }
462 }
463 last = pts[-1];
464 if (lastDirection == nextDirection) {
465 break; // colinear segment
466 }
467 // Possible values for corners are 2, 3, and 4.
468 // When corners == 3, nextDirection opposes firstDirection.
469 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000470 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
472 if ((directionCycle ^ turn) != nextDirection) {
473 return false; // direction didn't follow cycle
474 }
475 break;
476 }
477 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000478 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000479 case kCubic_Verb:
480 return false; // quadratic, cubic not allowed
481 case kMove_Verb:
482 last = *pts++;
483 closedOrMoved = true;
484 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000485 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000486 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000487 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000488 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000489 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000490 lastDirection = nextDirection;
491 }
492 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000493 bool result = 4 == corners && (first == last || autoClose);
494 if (savePts) {
495 *ptsPtr = savePts;
496 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000497 if (result && isClosed) {
498 *isClosed = autoClose;
499 }
500 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000501 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000502 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000503 return result;
504}
505
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000506bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000507 SkDEBUGCODE(this->validate();)
508 int currVerb = 0;
509 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000510 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
511 if (result && rect) {
512 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000513 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000514 return result;
515}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000516
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000517bool SkPath::isRect(bool* isClosed, Direction* direction) const {
518 SkDEBUGCODE(this->validate();)
519 int currVerb = 0;
520 const SkPoint* pts = fPathRef->points();
521 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000522}
523
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000524bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000525 SkDEBUGCODE(this->validate();)
526 int currVerb = 0;
527 const SkPoint* pts = fPathRef->points();
528 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000529 Direction testDirs[2];
530 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000531 return false;
532 }
533 const SkPoint* last = pts;
534 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000535 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000536 testRects[0].set(first, SkToS32(last - first));
537 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000538 if (testRects[0].contains(testRects[1])) {
539 if (rects) {
540 rects[0] = testRects[0];
541 rects[1] = testRects[1];
542 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000543 if (dirs) {
544 dirs[0] = testDirs[0];
545 dirs[1] = testDirs[1];
546 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000547 return true;
548 }
549 if (testRects[1].contains(testRects[0])) {
550 if (rects) {
551 rects[0] = testRects[1];
552 rects[1] = testRects[0];
553 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000554 if (dirs) {
555 dirs[0] = testDirs[1];
556 dirs[1] = testDirs[0];
557 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 return true;
559 }
560 }
561 return false;
562}
563
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000564int SkPath::countPoints() const {
565 return fPathRef->countPoints();
566}
567
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000568int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000569 SkDEBUGCODE(this->validate();)
570
571 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000572 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000573 int count = SkMin32(max, fPathRef->countPoints());
574 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
575 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000576}
577
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000578SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000579 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
580 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000581 }
582 return SkPoint::Make(0, 0);
583}
584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585int SkPath::countVerbs() const {
586 return fPathRef->countVerbs();
587}
588
589static inline void copy_verbs_reverse(uint8_t* inorderDst,
590 const uint8_t* reversedSrc,
591 int count) {
592 for (int i = 0; i < count; ++i) {
593 inorderDst[i] = reversedSrc[~i];
594 }
595}
596
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000597int SkPath::getVerbs(uint8_t dst[], int max) const {
598 SkDEBUGCODE(this->validate();)
599
600 SkASSERT(max >= 0);
601 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000602 int count = SkMin32(max, fPathRef->countVerbs());
603 copy_verbs_reverse(dst, fPathRef->verbs(), count);
604 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000605}
606
reed@google.com294dd7b2011-10-11 11:58:32 +0000607bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000608 SkDEBUGCODE(this->validate();)
609
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000610 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000611 if (count > 0) {
612 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000613 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000614 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000615 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000616 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000617 if (lastPt) {
618 lastPt->set(0, 0);
619 }
620 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000621}
622
623void SkPath::setLastPt(SkScalar x, SkScalar y) {
624 SkDEBUGCODE(this->validate();)
625
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000626 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000627 if (count == 0) {
628 this->moveTo(x, y);
629 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000630 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 SkPathRef::Editor ed(&fPathRef);
632 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 }
634}
635
reed@google.com04863fa2011-05-15 04:08:24 +0000636void SkPath::setConvexity(Convexity c) {
637 if (fConvexity != c) {
638 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000639 }
640}
641
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642//////////////////////////////////////////////////////////////////////////////
643// Construction methods
644
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000645#define DIRTY_AFTER_EDIT \
646 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000647 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000648 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000649 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000650 } while (0)
651
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652void SkPath::incReserve(U16CPU inc) {
653 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655 SkDEBUGCODE(this->validate();)
656}
657
658void SkPath::moveTo(SkScalar x, SkScalar y) {
659 SkDEBUGCODE(this->validate();)
660
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000661 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662
reed@google.comd335d1d2012-01-12 18:17:11 +0000663 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667}
668
669void SkPath::rMoveTo(SkScalar x, SkScalar y) {
670 SkPoint pt;
671 this->getLastPt(&pt);
672 this->moveTo(pt.fX + x, pt.fY + y);
673}
674
reed@google.comd335d1d2012-01-12 18:17:11 +0000675void SkPath::injectMoveToIfNeeded() {
676 if (fLastMoveToIndex < 0) {
677 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000678 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000679 x = y = 0;
680 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000682 x = pt.fX;
683 y = pt.fY;
684 }
685 this->moveTo(x, y);
686 }
687}
688
reed@android.com8a1c16f2008-12-17 15:59:43 +0000689void SkPath::lineTo(SkScalar x, SkScalar y) {
690 SkDEBUGCODE(this->validate();)
691
reed@google.comd335d1d2012-01-12 18:17:11 +0000692 this->injectMoveToIfNeeded();
693
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 SkPathRef::Editor ed(&fPathRef);
695 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000696 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697
reed@google.comb54455e2011-05-16 14:16:04 +0000698 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699}
700
701void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000702 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 SkPoint pt;
704 this->getLastPt(&pt);
705 this->lineTo(pt.fX + x, pt.fY + y);
706}
707
708void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
709 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000710
reed@google.comd335d1d2012-01-12 18:17:11 +0000711 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000712
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000713 SkPathRef::Editor ed(&fPathRef);
714 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 pts[0].set(x1, y1);
716 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000717 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000718
reed@google.comb54455e2011-05-16 14:16:04 +0000719 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720}
721
722void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000723 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724 SkPoint pt;
725 this->getLastPt(&pt);
726 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
727}
728
reed@google.com277c3f82013-05-31 15:17:50 +0000729void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
730 SkScalar w) {
731 // check for <= 0 or NaN with this test
732 if (!(w > 0)) {
733 this->lineTo(x2, y2);
734 } else if (!SkScalarIsFinite(w)) {
735 this->lineTo(x1, y1);
736 this->lineTo(x2, y2);
737 } else if (SK_Scalar1 == w) {
738 this->quadTo(x1, y1, x2, y2);
739 } else {
740 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000741
reed@google.com277c3f82013-05-31 15:17:50 +0000742 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000743
reed@google.com277c3f82013-05-31 15:17:50 +0000744 SkPathRef::Editor ed(&fPathRef);
745 SkPoint* pts = ed.growForConic(w);
746 pts[0].set(x1, y1);
747 pts[1].set(x2, y2);
748 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000749
reed@google.com277c3f82013-05-31 15:17:50 +0000750 DIRTY_AFTER_EDIT;
751 }
752}
753
754void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
755 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000756 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000757 SkPoint pt;
758 this->getLastPt(&pt);
759 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
760}
761
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
763 SkScalar x3, SkScalar y3) {
764 SkDEBUGCODE(this->validate();)
765
reed@google.comd335d1d2012-01-12 18:17:11 +0000766 this->injectMoveToIfNeeded();
767
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000768 SkPathRef::Editor ed(&fPathRef);
769 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770 pts[0].set(x1, y1);
771 pts[1].set(x2, y2);
772 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000773 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774
reed@google.comb54455e2011-05-16 14:16:04 +0000775 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000776}
777
778void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
779 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000780 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 SkPoint pt;
782 this->getLastPt(&pt);
783 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
784 pt.fX + x3, pt.fY + y3);
785}
786
787void SkPath::close() {
788 SkDEBUGCODE(this->validate();)
789
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000790 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000792 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 case kLine_Verb:
794 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000795 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000797 case kMove_Verb: {
798 SkPathRef::Editor ed(&fPathRef);
799 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000801 }
reed@google.com277c3f82013-05-31 15:17:50 +0000802 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000803 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000804 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000805 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000806 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000807 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 }
809 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000810
811 // signal that we need a moveTo to follow us (unless we're done)
812#if 0
813 if (fLastMoveToIndex >= 0) {
814 fLastMoveToIndex = ~fLastMoveToIndex;
815 }
816#else
817 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
818#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819}
820
821///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000822
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000823static void assert_known_direction(int dir) {
824 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
825}
826
reed@android.com8a1c16f2008-12-17 15:59:43 +0000827void SkPath::addRect(const SkRect& rect, Direction dir) {
828 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
829}
830
831void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
832 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000833 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000834 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
835 SkAutoDisableDirectionCheck addc(this);
836
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
838
839 this->incReserve(5);
840
841 this->moveTo(left, top);
842 if (dir == kCCW_Direction) {
843 this->lineTo(left, bottom);
844 this->lineTo(right, bottom);
845 this->lineTo(right, top);
846 } else {
847 this->lineTo(right, top);
848 this->lineTo(right, bottom);
849 this->lineTo(left, bottom);
850 }
851 this->close();
852}
853
reed@google.com744faba2012-05-29 19:54:52 +0000854void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
855 SkDEBUGCODE(this->validate();)
856 if (count <= 0) {
857 return;
858 }
859
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000860 SkPathRef::Editor ed(&fPathRef);
861 fLastMoveToIndex = ed.pathRef()->countPoints();
862 uint8_t* vb;
863 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000864 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000865 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000866
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000867 memcpy(p, pts, count * sizeof(SkPoint));
868 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000869 if (count > 1) {
870 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
871 // be 0, the compiler will remove the test/branch entirely.
872 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000873 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000874 } else {
875 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000876 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000877 }
878 }
879 fSegmentMask |= kLine_SegmentMask;
880 }
881 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000882 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000883 }
884
reed@google.com744faba2012-05-29 19:54:52 +0000885 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000886 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000887}
888
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000889#include "SkGeometry.h"
890
891static int build_arc_points(const SkRect& oval, SkScalar startAngle,
892 SkScalar sweepAngle,
893 SkPoint pts[kSkBuildQuadArcStorage]) {
894
895 if (0 == sweepAngle &&
896 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
897 // Chrome uses this path to move into and out of ovals. If not
898 // treated as a special case the moves can distort the oval's
899 // bounding box (and break the circle special case).
900 pts[0].set(oval.fRight, oval.centerY());
901 return 1;
902 } else if (0 == oval.width() && 0 == oval.height()) {
903 // Chrome will sometimes create 0 radius round rects. Having degenerate
904 // quad segments in the path prevents the path from being recognized as
905 // a rect.
906 // TODO: optimizing the case where only one of width or height is zero
907 // should also be considered. This case, however, doesn't seem to be
908 // as common as the single point case.
909 pts[0].set(oval.fRight, oval.fTop);
910 return 1;
911 }
912
913 SkVector start, stop;
914
915 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
916 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
917 &stop.fX);
918
919 /* If the sweep angle is nearly (but less than) 360, then due to precision
920 loss in radians-conversion and/or sin/cos, we may end up with coincident
921 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
922 of drawing a nearly complete circle (good).
923 e.g. canvas.drawArc(0, 359.99, ...)
924 -vs- canvas.drawArc(0, 359.9, ...)
925 We try to detect this edge case, and tweak the stop vector
926 */
927 if (start == stop) {
928 SkScalar sw = SkScalarAbs(sweepAngle);
929 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
930 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
931 // make a guess at a tiny angle (in radians) to tweak by
932 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
933 // not sure how much will be enough, so we use a loop
934 do {
935 stopRad -= deltaRad;
936 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
937 } while (start == stop);
938 }
939 }
940
941 SkMatrix matrix;
942
943 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
944 matrix.postTranslate(oval.centerX(), oval.centerY());
945
946 return SkBuildQuadArc(start, stop,
947 sweepAngle > 0 ? kCW_SkRotationDirection :
948 kCCW_SkRotationDirection,
949 &matrix, pts);
950}
951
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952static void add_corner_arc(SkPath* path, const SkRect& rect,
953 SkScalar rx, SkScalar ry, int startAngle,
robertphillips@google.com12367192013-10-20 13:11:16 +0000954 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000955 // These two asserts are not sufficient, since really we want to know
956 // that the pair of radii (e.g. left and right, or top and bottom) sum
957 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000958 // these conservative asserts.
959 SkASSERT(0 <= rx && rx <= rect.width());
960 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000961
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 SkRect r;
963 r.set(-rx, -ry, rx, ry);
964
965 switch (startAngle) {
966 case 0:
967 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
968 break;
969 case 90:
970 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
971 break;
972 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
973 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000974 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 }
reed@google.comabf15c12011-01-18 20:35:51 +0000976
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977 SkScalar start = SkIntToScalar(startAngle);
978 SkScalar sweep = SkIntToScalar(90);
979 if (SkPath::kCCW_Direction == dir) {
980 start += sweep;
981 sweep = -sweep;
982 }
reed@google.comabf15c12011-01-18 20:35:51 +0000983
robertphillips@google.com12367192013-10-20 13:11:16 +0000984 path->arcTo(r, start, sweep, forceMoveTo);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000985}
986
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000987void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000989 SkRRect rrect;
990 rrect.setRectRadii(rect, (const SkVector*) radii);
991 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992}
993
reed@google.com4ed0fb72012-12-12 20:48:18 +0000994void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000995 assert_known_direction(dir);
996
997 if (rrect.isEmpty()) {
998 return;
999 }
1000
reed@google.com4ed0fb72012-12-12 20:48:18 +00001001 const SkRect& bounds = rrect.getBounds();
1002
1003 if (rrect.isRect()) {
1004 this->addRect(bounds, dir);
1005 } else if (rrect.isOval()) {
1006 this->addOval(bounds, dir);
1007 } else if (rrect.isSimple()) {
1008 const SkVector& rad = rrect.getSimpleRadii();
1009 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1010 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001011 SkAutoPathBoundsUpdate apbu(this, bounds);
1012
1013 if (kCW_Direction == dir) {
robertphillips@google.com12367192013-10-20 13:11:16 +00001014 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1015 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1016 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1017 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001018 } else {
robertphillips@google.com12367192013-10-20 13:11:16 +00001019 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1020 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1021 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1022 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001023 }
1024 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001025 }
1026}
1027
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001028bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001029 int count = fPathRef->countVerbs();
1030 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1031 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001032 if (*verbs == kLine_Verb ||
1033 *verbs == kQuad_Verb ||
1034 *verbs == kCubic_Verb) {
1035 return false;
1036 }
1037 ++verbs;
1038 }
1039 return true;
1040}
1041
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001042#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001043#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001044#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001045
1046void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1047 Direction dir) {
1048 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001049
humper@google.com75e3ca12013-04-08 21:44:11 +00001050 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001051 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001052 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001053 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001054 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1055 return;
1056 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001057
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001058 SkScalar w = rect.width();
1059 SkScalar halfW = SkScalarHalf(w);
1060 SkScalar h = rect.height();
1061 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001062
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001063 if (halfW <= 0 || halfH <= 0) {
1064 return;
1065 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001066
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001067 bool skip_hori = rx >= halfW;
1068 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001069
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001070 if (skip_hori && skip_vert) {
1071 this->addOval(rect, dir);
1072 return;
1073 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001074
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001075 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001076
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001077 SkAutoPathBoundsUpdate apbu(this, rect);
1078 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001079
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001080 if (skip_hori) {
1081 rx = halfW;
1082 } else if (skip_vert) {
1083 ry = halfH;
1084 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001085
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001086#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001087 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1088 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001089
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001090 this->incReserve(17);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001092 // Please see SkBuildQuadArc for more information but, we need to pull
1093 // the off-curve quadratic points in a little bit to make the round
1094 // rects convex.
1095 static const SkScalar kOffCurveEpsilon = 0.0001f;
1096
1097 SkScalar midPtX = rx * SK_ScalarRoot2Over2;
1098 SkScalar midPtY = ry * SK_ScalarRoot2Over2;
1099
1100 SkScalar offPtX = rx * SK_ScalarTanPIOver8 - kOffCurveEpsilon;
1101 SkScalar offPtY = ry * SK_ScalarTanPIOver8 - kOffCurveEpsilon;
1102
1103 this->incReserve(21);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001104#endif
robertphillips@google.com12367192013-10-20 13:11:16 +00001105 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001106 if (dir == kCCW_Direction) {
1107 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001108 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001109 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001110#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001111 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1112 rect.fLeft, rect.fTop + ry - sy,
1113 rect.fLeft, rect.fTop + ry); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001114#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001115 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop + kOffCurveEpsilon,
1116 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
1117 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fTop + ry - offPtY,
1118 rect.fLeft, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001119#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001120 if (!skip_vert) {
1121 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1122 }
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, rect.fBottom - ry + sy,
1125 rect.fLeft + rx - sx, rect.fBottom,
1126 rect.fLeft + rx, rect.fBottom); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001127#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001128 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1129 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
1130 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom - kOffCurveEpsilon,
1131 rect.fLeft + rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001132#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001133 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001134 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001135 }
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.fRight - rx + sx, rect.fBottom,
1138 rect.fRight, rect.fBottom - ry + sy,
1139 rect.fRight, rect.fBottom - ry); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001140#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001141 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom - kOffCurveEpsilon,
1142 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
1143 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1144 rect.fRight, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001145#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001146 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001147 this->lineTo(rect.fRight, rect.fTop + ry); // right
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, rect.fTop + ry - sy,
1151 rect.fRight - rx + sx, rect.fTop,
1152 rect.fRight - rx, rect.fTop); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001153#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001154 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fTop + ry - offPtY,
1155 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
1156 this->quadTo(rect.fRight - rx + offPtX, rect.fTop + kOffCurveEpsilon,
1157 rect.fRight - rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001158#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001159 } else {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001160#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001161 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1162 rect.fRight, rect.fTop + ry - sy,
1163 rect.fRight, rect.fTop + ry); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001164#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001165 this->quadTo(rect.fRight - rx + offPtX, rect.fTop + kOffCurveEpsilon,
1166 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
1167 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fTop + ry - offPtY,
1168 rect.fRight, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001169#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001170 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001171 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001172 }
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, rect.fBottom - ry + sy,
1175 rect.fRight - rx + sx, rect.fBottom,
1176 rect.fRight - rx, rect.fBottom); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001177#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001178 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1179 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
1180 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom - kOffCurveEpsilon,
1181 rect.fRight - rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001182#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001183 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001184 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
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.fLeft + rx - sx, rect.fBottom,
1188 rect.fLeft, rect.fBottom - ry + sy,
1189 rect.fLeft, rect.fBottom - ry); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001190#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001191 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom - kOffCurveEpsilon,
1192 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
1193 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1194 rect.fLeft, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001195#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001196 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001197 this->lineTo(rect.fLeft, rect.fTop + ry); // left
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, rect.fTop + ry - sy,
1201 rect.fLeft + rx - sx, rect.fTop,
1202 rect.fLeft + rx, rect.fTop); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001203#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001204 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fTop + ry - offPtY,
1205 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
1206 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop + kOffCurveEpsilon,
1207 rect.fLeft + rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001208#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001209 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001210 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001211 }
1212 }
1213 this->close();
1214}
1215
reed@android.com8a1c16f2008-12-17 15:59:43 +00001216void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001217 assert_known_direction(dir);
1218
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001219 /* If addOval() is called after previous moveTo(),
1220 this path is still marked as an oval. This is used to
1221 fit into WebKit's calling sequences.
1222 We can't simply check isEmpty() in this case, as additional
1223 moveTo() would mark the path non empty.
1224 */
1225 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001226 if (fIsOval) {
1227 fDirection = dir;
1228 } else {
1229 fDirection = kUnknown_Direction;
1230 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001231
1232 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001233 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001234
reed@android.com8a1c16f2008-12-17 15:59:43 +00001235 SkAutoPathBoundsUpdate apbu(this, oval);
1236
1237 SkScalar cx = oval.centerX();
1238 SkScalar cy = oval.centerY();
1239 SkScalar rx = SkScalarHalf(oval.width());
1240 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001241
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1243 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1244 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1245 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1246
1247 /*
1248 To handle imprecision in computing the center and radii, we revert to
1249 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1250 to ensure that we don't exceed the oval's bounds *ever*, since we want
1251 to use oval for our fast-bounds, rather than have to recompute it.
1252 */
1253 const SkScalar L = oval.fLeft; // cx - rx
1254 const SkScalar T = oval.fTop; // cy - ry
1255 const SkScalar R = oval.fRight; // cx + rx
1256 const SkScalar B = oval.fBottom; // cy + ry
1257
1258 this->incReserve(17); // 8 quads + close
1259 this->moveTo(R, cy);
1260 if (dir == kCCW_Direction) {
1261 this->quadTo( R, cy - sy, cx + mx, cy - my);
1262 this->quadTo(cx + sx, T, cx , T);
1263 this->quadTo(cx - sx, T, cx - mx, cy - my);
1264 this->quadTo( L, cy - sy, L, cy );
1265 this->quadTo( L, cy + sy, cx - mx, cy + my);
1266 this->quadTo(cx - sx, B, cx , B);
1267 this->quadTo(cx + sx, B, cx + mx, cy + my);
1268 this->quadTo( R, cy + sy, R, cy );
1269 } else {
1270 this->quadTo( R, cy + sy, cx + mx, cy + my);
1271 this->quadTo(cx + sx, B, cx , B);
1272 this->quadTo(cx - sx, B, cx - mx, cy + my);
1273 this->quadTo( L, cy + sy, L, cy );
1274 this->quadTo( L, 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( R, cy - sy, R, cy );
1278 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 this->close();
1280}
1281
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001282bool SkPath::isOval(SkRect* rect) const {
1283 if (fIsOval && rect) {
1284 *rect = getBounds();
1285 }
1286
1287 return fIsOval;
1288}
1289
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1291 if (r > 0) {
1292 SkRect rect;
1293 rect.set(x - r, y - r, x + r, y + r);
1294 this->addOval(rect, dir);
1295 }
1296}
1297
reed@android.com8a1c16f2008-12-17 15:59:43 +00001298void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1299 bool forceMoveTo) {
1300 if (oval.width() < 0 || oval.height() < 0) {
1301 return;
1302 }
1303
1304 SkPoint pts[kSkBuildQuadArcStorage];
1305 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1306 SkASSERT((count & 1) == 1);
1307
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001308 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001309 forceMoveTo = true;
1310 }
1311 this->incReserve(count);
1312 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1313 for (int i = 1; i < count; i += 2) {
1314 this->quadTo(pts[i], pts[i+1]);
1315 }
1316}
1317
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001318void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 if (oval.isEmpty() || 0 == sweepAngle) {
1320 return;
1321 }
1322
1323 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1324
1325 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1326 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1327 return;
1328 }
1329
1330 SkPoint pts[kSkBuildQuadArcStorage];
1331 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1332
1333 this->incReserve(count);
1334 this->moveTo(pts[0]);
1335 for (int i = 1; i < count; i += 2) {
1336 this->quadTo(pts[i], pts[i+1]);
1337 }
1338}
1339
1340/*
1341 Need to handle the case when the angle is sharp, and our computed end-points
1342 for the arc go behind pt1 and/or p2...
1343*/
1344void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1345 SkScalar radius) {
1346 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001347
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 // need to know our prev pt so we can construct tangent vectors
1349 {
1350 SkPoint start;
1351 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001352 // Handle degenerate cases by adding a line to the first point and
1353 // bailing out.
1354 if ((x1 == start.fX && y1 == start.fY) ||
1355 (x1 == x2 && y1 == y2) ||
1356 radius == 0) {
1357 this->lineTo(x1, y1);
1358 return;
1359 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 before.setNormalize(x1 - start.fX, y1 - start.fY);
1361 after.setNormalize(x2 - x1, y2 - y1);
1362 }
reed@google.comabf15c12011-01-18 20:35:51 +00001363
reed@android.com8a1c16f2008-12-17 15:59:43 +00001364 SkScalar cosh = SkPoint::DotProduct(before, after);
1365 SkScalar sinh = SkPoint::CrossProduct(before, after);
1366
1367 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001368 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 return;
1370 }
reed@google.comabf15c12011-01-18 20:35:51 +00001371
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1373 if (dist < 0) {
1374 dist = -dist;
1375 }
1376
1377 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1378 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1379 SkRotationDirection arcDir;
1380
1381 // now turn before/after into normals
1382 if (sinh > 0) {
1383 before.rotateCCW();
1384 after.rotateCCW();
1385 arcDir = kCW_SkRotationDirection;
1386 } else {
1387 before.rotateCW();
1388 after.rotateCW();
1389 arcDir = kCCW_SkRotationDirection;
1390 }
1391
1392 SkMatrix matrix;
1393 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001394
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 matrix.setScale(radius, radius);
1396 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1397 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001398
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001400
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 this->incReserve(count);
1402 // [xx,yy] == pts[0]
1403 this->lineTo(xx, yy);
1404 for (int i = 1; i < count; i += 2) {
1405 this->quadTo(pts[i], pts[i+1]);
1406 }
1407}
1408
1409///////////////////////////////////////////////////////////////////////////////
1410
1411void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1412 SkMatrix matrix;
1413
1414 matrix.setTranslate(dx, dy);
1415 this->addPath(path, matrix);
1416}
1417
1418void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001419 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001421 fIsOval = false;
1422
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001423 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 SkPoint pts[4];
1425 Verb verb;
1426
1427 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1428
1429 while ((verb = iter.next(pts)) != kDone_Verb) {
1430 switch (verb) {
1431 case kMove_Verb:
1432 proc(matrix, &pts[0], &pts[0], 1);
1433 this->moveTo(pts[0]);
1434 break;
1435 case kLine_Verb:
1436 proc(matrix, &pts[1], &pts[1], 1);
1437 this->lineTo(pts[1]);
1438 break;
1439 case kQuad_Verb:
1440 proc(matrix, &pts[1], &pts[1], 2);
1441 this->quadTo(pts[1], pts[2]);
1442 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001443 case kConic_Verb:
1444 proc(matrix, &pts[1], &pts[1], 2);
1445 this->conicTo(pts[1], pts[2], iter.conicWeight());
1446 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001447 case kCubic_Verb:
1448 proc(matrix, &pts[1], &pts[1], 3);
1449 this->cubicTo(pts[1], pts[2], pts[3]);
1450 break;
1451 case kClose_Verb:
1452 this->close();
1453 break;
1454 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001455 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 }
1457 }
1458}
1459
1460///////////////////////////////////////////////////////////////////////////////
1461
reed@google.com277c3f82013-05-31 15:17:50 +00001462static int pts_in_verb(unsigned verb) {
1463 static const uint8_t gPtsInVerb[] = {
1464 1, // kMove
1465 1, // kLine
1466 2, // kQuad
1467 2, // kConic
1468 3, // kCubic
1469 0, // kClose
1470 0 // kDone
1471 };
1472
1473 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1474 return gPtsInVerb[verb];
1475}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476
1477// ignore the initial moveto, and stop when the 1st contour ends
1478void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001479 int i, vcount = path.fPathRef->countVerbs();
1480 // exit early if the path is empty, or just has a moveTo.
1481 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 return;
1483 }
1484
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001485 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001487 fIsOval = false;
1488
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001489 const uint8_t* verbs = path.fPathRef->verbs();
1490 // skip the initial moveTo
1491 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001492 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001494 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001496 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 case kLine_Verb:
1498 this->lineTo(pts[0].fX, pts[0].fY);
1499 break;
1500 case kQuad_Verb:
1501 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1502 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001503 case kConic_Verb:
1504 this->conicTo(pts[0], pts[1], *conicWeight++);
1505 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 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 +00001508 break;
1509 case kClose_Verb:
1510 return;
1511 }
reed@google.com277c3f82013-05-31 15:17:50 +00001512 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 }
1514}
1515
1516// ignore the last point of the 1st contour
1517void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 int i, vcount = path.fPathRef->countVerbs();
1519 // exit early if the path is empty, or just has a moveTo.
1520 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 return;
1522 }
1523
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001526 fIsOval = false;
1527
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 const uint8_t* verbs = path.fPathRef->verbs();
1529 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001530 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001532 SkASSERT(verbs[~0] == kMove_Verb);
1533 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001534 unsigned v = verbs[~i];
1535 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 if (n == 0) {
1537 break;
1538 }
1539 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001540 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 }
1542
1543 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001544 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 case kLine_Verb:
1546 this->lineTo(pts[-1].fX, pts[-1].fY);
1547 break;
1548 case kQuad_Verb:
1549 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1550 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001551 case kConic_Verb:
1552 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1553 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 case kCubic_Verb:
1555 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1556 pts[-3].fX, pts[-3].fY);
1557 break;
1558 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001559 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 break;
1561 }
reed@google.com277c3f82013-05-31 15:17:50 +00001562 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563 }
1564}
1565
reed@google.com63d73742012-01-10 15:33:12 +00001566void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001567 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001568
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 const SkPoint* pts = src.fPathRef->pointsEnd();
1570 // we will iterator through src's verbs backwards
1571 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1572 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001573 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001574
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001575 fIsOval = false;
1576
reed@google.com63d73742012-01-10 15:33:12 +00001577 bool needMove = true;
1578 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001579 while (verbs < verbsEnd) {
1580 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001581 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001582
1583 if (needMove) {
1584 --pts;
1585 this->moveTo(pts->fX, pts->fY);
1586 needMove = false;
1587 }
1588 pts -= n;
1589 switch (v) {
1590 case kMove_Verb:
1591 if (needClose) {
1592 this->close();
1593 needClose = false;
1594 }
1595 needMove = true;
1596 pts += 1; // so we see the point in "if (needMove)" above
1597 break;
1598 case kLine_Verb:
1599 this->lineTo(pts[0]);
1600 break;
1601 case kQuad_Verb:
1602 this->quadTo(pts[1], pts[0]);
1603 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001604 case kConic_Verb:
1605 this->conicTo(pts[1], pts[0], *--conicWeights);
1606 break;
reed@google.com63d73742012-01-10 15:33:12 +00001607 case kCubic_Verb:
1608 this->cubicTo(pts[2], pts[1], pts[0]);
1609 break;
1610 case kClose_Verb:
1611 needClose = true;
1612 break;
1613 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001614 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001615 }
1616 }
1617}
1618
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619///////////////////////////////////////////////////////////////////////////////
1620
1621void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1622 SkMatrix matrix;
1623
1624 matrix.setTranslate(dx, dy);
1625 this->transform(matrix, dst);
1626}
1627
1628#include "SkGeometry.h"
1629
1630static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1631 int level = 2) {
1632 if (--level >= 0) {
1633 SkPoint tmp[5];
1634
1635 SkChopQuadAtHalf(pts, tmp);
1636 subdivide_quad_to(path, &tmp[0], level);
1637 subdivide_quad_to(path, &tmp[2], level);
1638 } else {
1639 path->quadTo(pts[1], pts[2]);
1640 }
1641}
1642
1643static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1644 int level = 2) {
1645 if (--level >= 0) {
1646 SkPoint tmp[7];
1647
1648 SkChopCubicAtHalf(pts, tmp);
1649 subdivide_cubic_to(path, &tmp[0], level);
1650 subdivide_cubic_to(path, &tmp[3], level);
1651 } else {
1652 path->cubicTo(pts[1], pts[2], pts[3]);
1653 }
1654}
1655
1656void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1657 SkDEBUGCODE(this->validate();)
1658 if (dst == NULL) {
1659 dst = (SkPath*)this;
1660 }
1661
tomhudson@google.com8d430182011-06-06 19:11:19 +00001662 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 SkPath tmp;
1664 tmp.fFillType = fFillType;
1665
1666 SkPath::Iter iter(*this, false);
1667 SkPoint pts[4];
1668 SkPath::Verb verb;
1669
reed@google.com4a3b7142012-05-16 17:16:46 +00001670 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 switch (verb) {
1672 case kMove_Verb:
1673 tmp.moveTo(pts[0]);
1674 break;
1675 case kLine_Verb:
1676 tmp.lineTo(pts[1]);
1677 break;
1678 case kQuad_Verb:
1679 subdivide_quad_to(&tmp, pts);
1680 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001681 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001682 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001683 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1684 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001685 case kCubic_Verb:
1686 subdivide_cubic_to(&tmp, pts);
1687 break;
1688 case kClose_Verb:
1689 tmp.close();
1690 break;
1691 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001692 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693 break;
1694 }
1695 }
1696
1697 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001698 SkPathRef::Editor ed(&dst->fPathRef);
1699 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001700 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001702 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1703
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001706 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001707 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001709
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001710 if (kUnknown_Direction == fDirection) {
1711 dst->fDirection = kUnknown_Direction;
1712 } else {
1713 SkScalar det2x2 =
1714 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1715 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1716 if (det2x2 < 0) {
1717 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1718 } else if (det2x2 > 0) {
1719 dst->fDirection = fDirection;
1720 } else {
1721 dst->fDirection = kUnknown_Direction;
1722 }
1723 }
1724
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001725 // It's an oval only if it stays a rect.
1726 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001727
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 SkDEBUGCODE(dst->validate();)
1729 }
1730}
1731
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732///////////////////////////////////////////////////////////////////////////////
1733///////////////////////////////////////////////////////////////////////////////
1734
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001736 kEmptyContour_SegmentState, // The current contour is empty. We may be
1737 // starting processing or we may have just
1738 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1740 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1741 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742};
1743
1744SkPath::Iter::Iter() {
1745#ifdef SK_DEBUG
1746 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001747 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001749 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001750 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751#endif
1752 // need to init enough to make next() harmlessly return kDone_Verb
1753 fVerbs = NULL;
1754 fVerbStop = NULL;
1755 fNeedClose = false;
1756}
1757
1758SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1759 this->setPath(path, forceClose);
1760}
1761
1762void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001763 fPts = path.fPathRef->points();
1764 fVerbs = path.fPathRef->verbs();
1765 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001766 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001767 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001768 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 fForceClose = SkToU8(forceClose);
1770 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001771 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772}
1773
1774bool SkPath::Iter::isClosedContour() const {
1775 if (fVerbs == NULL || fVerbs == fVerbStop) {
1776 return false;
1777 }
1778 if (fForceClose) {
1779 return true;
1780 }
1781
1782 const uint8_t* verbs = fVerbs;
1783 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001785 if (kMove_Verb == *(verbs - 1)) {
1786 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 }
1788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001789 while (verbs > stop) {
1790 // verbs points one beyond the current verb, decrement first.
1791 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 if (kMove_Verb == v) {
1793 break;
1794 }
1795 if (kClose_Verb == v) {
1796 return true;
1797 }
1798 }
1799 return false;
1800}
1801
1802SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001803 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001805 // A special case: if both points are NaN, SkPoint::operation== returns
1806 // false, but the iterator expects that they are treated as the same.
1807 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001808 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1809 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001810 return kClose_Verb;
1811 }
1812
reed@google.com9e25dbf2012-05-15 17:05:38 +00001813 pts[0] = fLastPt;
1814 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 fLastPt = fMoveTo;
1816 fCloseLine = true;
1817 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001818 } else {
1819 pts[0] = fMoveTo;
1820 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822}
1823
reed@google.com9e25dbf2012-05-15 17:05:38 +00001824const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 if (fSegmentState == kAfterMove_SegmentState) {
1826 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001828 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1831 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834}
1835
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001836void SkPath::Iter::consumeDegenerateSegments() {
1837 // We need to step over anything that will not move the current draw point
1838 // forward before the next move is seen
1839 const uint8_t* lastMoveVerb = 0;
1840 const SkPoint* lastMovePt = 0;
1841 SkPoint lastPt = fLastPt;
1842 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001843 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 switch (verb) {
1845 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 // Keep a record of this most recent move
1847 lastMoveVerb = fVerbs;
1848 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001849 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 fPts++;
1852 break;
1853
1854 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001855 // A close when we are in a segment is always valid except when it
1856 // follows a move which follows a segment.
1857 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 return;
1859 }
1860 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001861 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 break;
1863
1864 case kLine_Verb:
1865 if (!IsLineDegenerate(lastPt, fPts[0])) {
1866 if (lastMoveVerb) {
1867 fVerbs = lastMoveVerb;
1868 fPts = lastMovePt;
1869 return;
1870 }
1871 return;
1872 }
1873 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001874 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 fPts++;
1876 break;
1877
reed@google.com277c3f82013-05-31 15:17:50 +00001878 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 case kQuad_Verb:
1880 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1881 if (lastMoveVerb) {
1882 fVerbs = lastMoveVerb;
1883 fPts = lastMovePt;
1884 return;
1885 }
1886 return;
1887 }
1888 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001889 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001891 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 break;
1893
1894 case kCubic_Verb:
1895 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1896 if (lastMoveVerb) {
1897 fVerbs = lastMoveVerb;
1898 fPts = lastMovePt;
1899 return;
1900 }
1901 return;
1902 }
1903 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 fPts += 3;
1906 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001907
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001909 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 }
1911 }
1912}
1913
reed@google.com4a3b7142012-05-16 17:16:46 +00001914SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 // Close the curve if requested and if there is some curve to close
1919 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001920 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 return kLine_Verb;
1922 }
1923 fNeedClose = false;
1924 return kClose_Verb;
1925 }
1926 return kDone_Verb;
1927 }
1928
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001929 // fVerbs is one beyond the current verb, decrement first
1930 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001931 const SkPoint* SK_RESTRICT srcPts = fPts;
1932 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933
1934 switch (verb) {
1935 case kMove_Verb:
1936 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001937 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 verb = this->autoClose(pts);
1939 if (verb == kClose_Verb) {
1940 fNeedClose = false;
1941 }
1942 return (Verb)verb;
1943 }
1944 if (fVerbs == fVerbStop) { // might be a trailing moveto
1945 return kDone_Verb;
1946 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001947 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001950 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 fNeedClose = fForceClose;
1953 break;
1954 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955 pts[0] = this->cons_moveTo();
1956 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 fLastPt = srcPts[0];
1958 fCloseLine = false;
1959 srcPts += 1;
1960 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001961 case kConic_Verb:
1962 fConicWeights += 1;
1963 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001965 pts[0] = this->cons_moveTo();
1966 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 fLastPt = srcPts[1];
1968 srcPts += 2;
1969 break;
1970 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001971 pts[0] = this->cons_moveTo();
1972 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 fLastPt = srcPts[2];
1974 srcPts += 3;
1975 break;
1976 case kClose_Verb:
1977 verb = this->autoClose(pts);
1978 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001979 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 } else {
1981 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001982 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001983 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001984 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 break;
1986 }
1987 fPts = srcPts;
1988 return (Verb)verb;
1989}
1990
1991///////////////////////////////////////////////////////////////////////////////
1992
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001993SkPath::RawIter::RawIter() {
1994#ifdef SK_DEBUG
1995 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001996 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001997 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1998#endif
1999 // need to init enough to make next() harmlessly return kDone_Verb
2000 fVerbs = NULL;
2001 fVerbStop = NULL;
2002}
2003
2004SkPath::RawIter::RawIter(const SkPath& path) {
2005 this->setPath(path);
2006}
2007
2008void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002009 fPts = path.fPathRef->points();
2010 fVerbs = path.fPathRef->verbs();
2011 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002012 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002013 fMoveTo.fX = fMoveTo.fY = 0;
2014 fLastPt.fX = fLastPt.fY = 0;
2015}
2016
2017SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002018 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019 if (fVerbs == fVerbStop) {
2020 return kDone_Verb;
2021 }
2022
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002023 // fVerbs points one beyond next verb so decrement first.
2024 unsigned verb = *(--fVerbs);
2025 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002026
2027 switch (verb) {
2028 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002029 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002030 fMoveTo = srcPts[0];
2031 fLastPt = fMoveTo;
2032 srcPts += 1;
2033 break;
2034 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002035 pts[0] = fLastPt;
2036 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002037 fLastPt = srcPts[0];
2038 srcPts += 1;
2039 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002040 case kConic_Verb:
2041 fConicWeights += 1;
2042 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002043 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002044 pts[0] = fLastPt;
2045 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002046 fLastPt = srcPts[1];
2047 srcPts += 2;
2048 break;
2049 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002050 pts[0] = fLastPt;
2051 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002052 fLastPt = srcPts[2];
2053 srcPts += 3;
2054 break;
2055 case kClose_Verb:
2056 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002057 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002058 break;
2059 }
2060 fPts = srcPts;
2061 return (Verb)verb;
2062}
2063
2064///////////////////////////////////////////////////////////////////////////////
2065
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002067 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068*/
2069
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002070uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071 SkDEBUGCODE(this->validate();)
2072
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002073 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002074 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002075 return SkAlign4(byteCount);
2076 }
2077
2078 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002079
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002080 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081 (fConvexity << kConvexity_SerializationShift) |
2082 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002083 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002084 (fDirection << kDirection_SerializationShift)
2085#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2086 | (0x1 << kNewFormat_SerializationShift);
2087#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002088
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002089 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002090
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002091 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002092
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002093 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002094 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002095}
2096
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002097uint32_t SkPath::readFromMemory(const void* storage) {
2098 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002099
reed@google.com98b11f12011-09-21 18:40:27 +00002100 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002101 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2102 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2103 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002104 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002105 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002106#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2107 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2108#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002109
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002110 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2111#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2112 , newFormat, packed)
2113#endif
2114 );
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002115
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002116 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002117
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002119 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120}
2121
2122///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123
reed@google.com51bbe752013-01-17 22:07:50 +00002124#include "SkString.h"
2125
2126static void append_scalar(SkString* str, SkScalar value) {
2127 SkString tmp;
2128 tmp.printf("%g", value);
2129 if (tmp.contains('.')) {
2130 tmp.appendUnichar('f');
2131 }
2132 str->append(tmp);
2133}
2134
2135static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002136 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002137 str->append(label);
2138 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002139
reed@google.com51bbe752013-01-17 22:07:50 +00002140 const SkScalar* values = &pts[0].fX;
2141 count *= 2;
2142
2143 for (int i = 0; i < count; ++i) {
2144 append_scalar(str, values[i]);
2145 if (i < count - 1) {
2146 str->append(", ");
2147 }
2148 }
reed@google.com277c3f82013-05-31 15:17:50 +00002149 if (conicWeight >= 0) {
2150 str->append(", ");
2151 append_scalar(str, conicWeight);
2152 }
reed@google.com51bbe752013-01-17 22:07:50 +00002153 str->append(");\n");
2154}
2155
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156void SkPath::dump(bool forceClose, const char title[]) const {
2157 Iter iter(*this, forceClose);
2158 SkPoint pts[4];
2159 Verb verb;
2160
2161 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2162 title ? title : "");
2163
reed@google.com51bbe752013-01-17 22:07:50 +00002164 SkString builder;
2165
reed@google.com4a3b7142012-05-16 17:16:46 +00002166 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002167 switch (verb) {
2168 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002169 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170 break;
2171 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002172 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 break;
2174 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002175 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002176 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002177 case kConic_Verb:
2178 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2179 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002181 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182 break;
2183 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002184 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002185 break;
2186 default:
2187 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2188 verb = kDone_Verb; // stop the loop
2189 break;
2190 }
2191 }
reed@google.com51bbe752013-01-17 22:07:50 +00002192 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193}
2194
reed@android.come522ca52009-11-23 20:10:41 +00002195void SkPath::dump() const {
2196 this->dump(false);
2197}
2198
2199#ifdef SK_DEBUG
2200void SkPath::validate() const {
2201 SkASSERT(this != NULL);
2202 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002203
djsollen@google.com077348c2012-10-22 20:23:32 +00002204#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002205 if (!fBoundsIsDirty) {
2206 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002207
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002208 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002209 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002210
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002211 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002212 // if we're empty, fBounds may be empty but translated, so we can't
2213 // necessarily compare to bounds directly
2214 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2215 // be [2, 2, 2, 2]
2216 SkASSERT(bounds.isEmpty());
2217 SkASSERT(fBounds.isEmpty());
2218 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002219 if (bounds.isEmpty()) {
2220 SkASSERT(fBounds.isEmpty());
2221 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002222 if (!fBounds.isEmpty()) {
2223 SkASSERT(fBounds.contains(bounds));
2224 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002225 }
reed@android.come522ca52009-11-23 20:10:41 +00002226 }
2227 }
reed@google.com10296cc2011-09-21 12:29:05 +00002228
2229 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002230 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2231 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2232 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002233 case kLine_Verb:
2234 mask |= kLine_SegmentMask;
2235 break;
2236 case kQuad_Verb:
2237 mask |= kQuad_SegmentMask;
2238 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002239 case kConic_Verb:
2240 mask |= kConic_SegmentMask;
2241 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002242 case kCubic_Verb:
2243 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002244 case kMove_Verb: // these verbs aren't included in the segment mask.
2245 case kClose_Verb:
2246 break;
2247 case kDone_Verb:
2248 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2249 break;
2250 default:
2251 SkDEBUGFAIL("Unknown Verb");
2252 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002253 }
2254 }
2255 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002256#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002257}
djsollen@google.com077348c2012-10-22 20:23:32 +00002258#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002259
reed@google.com04863fa2011-05-15 04:08:24 +00002260///////////////////////////////////////////////////////////////////////////////
2261
reed@google.com0b7b9822011-05-16 12:29:27 +00002262static int sign(SkScalar x) { return x < 0; }
2263#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002264
reed@google.com04863fa2011-05-15 04:08:24 +00002265static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002266 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002267}
2268
2269// only valid for a single contour
2270struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002271 Convexicator()
2272 : fPtCount(0)
2273 , fConvexity(SkPath::kConvex_Convexity)
2274 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002275 fSign = 0;
2276 // warnings
2277 fCurrPt.set(0, 0);
2278 fVec0.set(0, 0);
2279 fVec1.set(0, 0);
2280 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002281
2282 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002283 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 }
2285
2286 SkPath::Convexity getConvexity() const { return fConvexity; }
2287
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002288 /** The direction returned is only valid if the path is determined convex */
2289 SkPath::Direction getDirection() const { return fDirection; }
2290
reed@google.com04863fa2011-05-15 04:08:24 +00002291 void addPt(const SkPoint& pt) {
2292 if (SkPath::kConcave_Convexity == fConvexity) {
2293 return;
2294 }
2295
2296 if (0 == fPtCount) {
2297 fCurrPt = pt;
2298 ++fPtCount;
2299 } else {
2300 SkVector vec = pt - fCurrPt;
2301 if (vec.fX || vec.fY) {
2302 fCurrPt = pt;
2303 if (++fPtCount == 2) {
2304 fFirstVec = fVec1 = vec;
2305 } else {
2306 SkASSERT(fPtCount > 2);
2307 this->addVec(vec);
2308 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002309
reed@google.com85b6e392011-05-15 20:25:17 +00002310 int sx = sign(vec.fX);
2311 int sy = sign(vec.fY);
2312 fDx += (sx != fSx);
2313 fDy += (sy != fSy);
2314 fSx = sx;
2315 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002316
reed@google.com85b6e392011-05-15 20:25:17 +00002317 if (fDx > 3 || fDy > 3) {
2318 fConvexity = SkPath::kConcave_Convexity;
2319 }
reed@google.com04863fa2011-05-15 04:08:24 +00002320 }
2321 }
2322 }
2323
2324 void close() {
2325 if (fPtCount > 2) {
2326 this->addVec(fFirstVec);
2327 }
2328 }
2329
2330private:
2331 void addVec(const SkVector& vec) {
2332 SkASSERT(vec.fX || vec.fY);
2333 fVec0 = fVec1;
2334 fVec1 = vec;
2335 int sign = CrossProductSign(fVec0, fVec1);
2336 if (0 == fSign) {
2337 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002338 if (1 == sign) {
2339 fDirection = SkPath::kCW_Direction;
2340 } else if (-1 == sign) {
2341 fDirection = SkPath::kCCW_Direction;
2342 }
reed@google.com04863fa2011-05-15 04:08:24 +00002343 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002344 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002345 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002346 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002347 }
2348 }
2349 }
2350
2351 SkPoint fCurrPt;
2352 SkVector fVec0, fVec1, fFirstVec;
2353 int fPtCount; // non-degenerate points
2354 int fSign;
2355 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002356 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002357 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002358};
2359
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002360SkPath::Convexity SkPath::internalGetConvexity() const {
2361 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002362 SkPoint pts[4];
2363 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002364 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002365
2366 int contourCount = 0;
2367 int count;
2368 Convexicator state;
2369
2370 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2371 switch (verb) {
2372 case kMove_Verb:
2373 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002374 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002375 return kConcave_Convexity;
2376 }
2377 pts[1] = pts[0];
2378 count = 1;
2379 break;
2380 case kLine_Verb: count = 1; break;
2381 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002382 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002383 case kCubic_Verb: count = 3; break;
2384 case kClose_Verb:
2385 state.close();
2386 count = 0;
2387 break;
2388 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002389 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002390 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002391 return kConcave_Convexity;
2392 }
2393
2394 for (int i = 1; i <= count; i++) {
2395 state.addPt(pts[i]);
2396 }
2397 // early exit
2398 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002399 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002400 return kConcave_Convexity;
2401 }
2402 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002403 fConvexity = state.getConvexity();
2404 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2405 fDirection = state.getDirection();
2406 }
2407 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002408}
reed@google.com69a99432012-01-10 18:00:10 +00002409
2410///////////////////////////////////////////////////////////////////////////////
2411
2412class ContourIter {
2413public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002414 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002415
2416 bool done() const { return fDone; }
2417 // if !done() then these may be called
2418 int count() const { return fCurrPtCount; }
2419 const SkPoint* pts() const { return fCurrPt; }
2420 void next();
2421
2422private:
2423 int fCurrPtCount;
2424 const SkPoint* fCurrPt;
2425 const uint8_t* fCurrVerb;
2426 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002427 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002428 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002429 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002430};
2431
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002432ContourIter::ContourIter(const SkPathRef& pathRef) {
2433 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002434 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002435 fCurrPt = pathRef.points();
2436 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002437 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002438 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002439 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002440 this->next();
2441}
2442
2443void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002444 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002445 fDone = true;
2446 }
2447 if (fDone) {
2448 return;
2449 }
2450
2451 // skip pts of prev contour
2452 fCurrPt += fCurrPtCount;
2453
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002454 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002455 int ptCount = 1; // moveTo
2456 const uint8_t* verbs = fCurrVerb;
2457
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002458 for (--verbs; verbs > fStopVerbs; --verbs) {
2459 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002460 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002461 goto CONTOUR_END;
2462 case SkPath::kLine_Verb:
2463 ptCount += 1;
2464 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002465 case SkPath::kConic_Verb:
2466 fCurrConicWeight += 1;
2467 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002468 case SkPath::kQuad_Verb:
2469 ptCount += 2;
2470 break;
2471 case SkPath::kCubic_Verb:
2472 ptCount += 3;
2473 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002474 case SkPath::kClose_Verb:
2475 break;
2476 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002477 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002478 break;
2479 }
2480 }
2481CONTOUR_END:
2482 fCurrPtCount = ptCount;
2483 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002484 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002485}
2486
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002487// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002488static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002489 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2490 // We may get 0 when the above subtracts underflow. We expect this to be
2491 // very rare and lazily promote to double.
2492 if (0 == cross) {
2493 double p0x = SkScalarToDouble(p0.fX);
2494 double p0y = SkScalarToDouble(p0.fY);
2495
2496 double p1x = SkScalarToDouble(p1.fX);
2497 double p1y = SkScalarToDouble(p1.fY);
2498
2499 double p2x = SkScalarToDouble(p2.fX);
2500 double p2y = SkScalarToDouble(p2.fY);
2501
2502 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2503 (p1y - p0y) * (p2x - p0x));
2504
2505 }
2506 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002507}
2508
reed@google.comc1ea60a2012-01-31 15:15:36 +00002509// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002510static int find_max_y(const SkPoint pts[], int count) {
2511 SkASSERT(count > 0);
2512 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002513 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002514 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002515 SkScalar y = pts[i].fY;
2516 if (y > max) {
2517 max = y;
2518 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002519 }
2520 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002521 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002522}
2523
reed@google.comcabaf1d2012-01-11 21:03:05 +00002524static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2525 int i = index;
2526 for (;;) {
2527 i = (i + inc) % n;
2528 if (i == index) { // we wrapped around, so abort
2529 break;
2530 }
2531 if (pts[index] != pts[i]) { // found a different point, success!
2532 break;
2533 }
2534 }
2535 return i;
2536}
2537
reed@google.comc1ea60a2012-01-31 15:15:36 +00002538/**
2539 * Starting at index, and moving forward (incrementing), find the xmin and
2540 * xmax of the contiguous points that have the same Y.
2541 */
2542static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2543 int* maxIndexPtr) {
2544 const SkScalar y = pts[index].fY;
2545 SkScalar min = pts[index].fX;
2546 SkScalar max = min;
2547 int minIndex = index;
2548 int maxIndex = index;
2549 for (int i = index + 1; i < n; ++i) {
2550 if (pts[i].fY != y) {
2551 break;
2552 }
2553 SkScalar x = pts[i].fX;
2554 if (x < min) {
2555 min = x;
2556 minIndex = i;
2557 } else if (x > max) {
2558 max = x;
2559 maxIndex = i;
2560 }
2561 }
2562 *maxIndexPtr = maxIndex;
2563 return minIndex;
2564}
2565
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002566static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002567 if (dir) {
2568 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2569 }
reed@google.comac8543f2012-01-30 20:51:25 +00002570}
2571
reed@google.comc1ea60a2012-01-31 15:15:36 +00002572#if 0
2573#include "SkString.h"
2574#include "../utils/SkParsePath.h"
2575static void dumpPath(const SkPath& path) {
2576 SkString str;
2577 SkParsePath::ToSVGString(path, &str);
2578 SkDebugf("%s\n", str.c_str());
2579}
2580#endif
2581
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002582namespace {
2583// for use with convex_dir_test
2584double mul(double a, double b) { return a * b; }
2585SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2586double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2587SkScalar toScalar(SkScalar a) { return a; }
2588
2589// determines the winding direction of a convex polygon with the precision
2590// of T. CAST_SCALAR casts an SkScalar to T.
2591template <typename T, T (CAST_SCALAR)(SkScalar)>
2592bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2593 // we find the first three points that form a non-degenerate
2594 // triangle. If there are no such points then the path is
2595 // degenerate. The first is always point 0. Now we find the second
2596 // point.
2597 int i = 0;
2598 enum { kX = 0, kY = 1 };
2599 T v0[2];
2600 while (1) {
2601 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2602 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2603 if (v0[kX] || v0[kY]) {
2604 break;
2605 }
2606 if (++i == n - 1) {
2607 return false;
2608 }
2609 }
2610 // now find a third point that is not colinear with the first two
2611 // points and check the orientation of the triangle (which will be
2612 // the same as the orientation of the path).
2613 for (++i; i < n; ++i) {
2614 T v1[2];
2615 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2616 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2617 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2618 if (0 != cross) {
2619 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2620 return true;
2621 }
2622 }
2623 return false;
2624}
2625}
2626
reed@google.comac8543f2012-01-30 20:51:25 +00002627/*
2628 * We loop through all contours, and keep the computed cross-product of the
2629 * contour that contained the global y-max. If we just look at the first
2630 * contour, we may find one that is wound the opposite way (correctly) since
2631 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2632 * that is outer most (or at least has the global y-max) before we can consider
2633 * its cross product.
2634 */
reed@google.com69a99432012-01-10 18:00:10 +00002635bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002636// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002637 // don't want to pay the cost for computing this if it
2638 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002639
2640 if (kUnknown_Direction != fDirection) {
2641 *dir = static_cast<Direction>(fDirection);
2642 return true;
2643 }
reed@google.com69a99432012-01-10 18:00:10 +00002644 const Convexity conv = this->getConvexityOrUnknown();
2645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002646 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002647
reed@google.comac8543f2012-01-30 20:51:25 +00002648 // initialize with our logical y-min
2649 SkScalar ymax = this->getBounds().fTop;
2650 SkScalar ymaxCross = 0;
2651
reed@google.com69a99432012-01-10 18:00:10 +00002652 for (; !iter.done(); iter.next()) {
2653 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002654 if (n < 3) {
2655 continue;
2656 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002657
reed@google.comcabaf1d2012-01-11 21:03:05 +00002658 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002659 SkScalar cross = 0;
2660 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002661 // We try first at scalar precision, and then again at double
2662 // precision. This is because the vectors computed between distant
2663 // points may lose too much precision.
2664 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002665 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002666 return true;
2667 }
2668 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002669 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002670 return true;
2671 } else {
2672 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002673 }
2674 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002675 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002676 if (pts[index].fY < ymax) {
2677 continue;
2678 }
2679
reed@google.comc1ea60a2012-01-31 15:15:36 +00002680 // If there is more than 1 distinct point at the y-max, we take the
2681 // x-min and x-max of them and just subtract to compute the dir.
2682 if (pts[(index + 1) % n].fY == pts[index].fY) {
2683 int maxIndex;
2684 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002685 if (minIndex == maxIndex) {
2686 goto TRY_CROSSPROD;
2687 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002688 SkASSERT(pts[minIndex].fY == pts[index].fY);
2689 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2690 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2691 // we just subtract the indices, and let that auto-convert to
2692 // SkScalar, since we just want - or + to signal the direction.
2693 cross = minIndex - maxIndex;
2694 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002695 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002696 // Find a next and prev index to use for the cross-product test,
2697 // but we try to find pts that form non-zero vectors from pts[index]
2698 //
2699 // Its possible that we can't find two non-degenerate vectors, so
2700 // we have to guard our search (e.g. all the pts could be in the
2701 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002702
reed@google.comc1ea60a2012-01-31 15:15:36 +00002703 // we pass n - 1 instead of -1 so we don't foul up % operator by
2704 // passing it a negative LH argument.
2705 int prev = find_diff_pt(pts, index, n, n - 1);
2706 if (prev == index) {
2707 // completely degenerate, skip to next contour
2708 continue;
2709 }
2710 int next = find_diff_pt(pts, index, n, 1);
2711 SkASSERT(next != index);
2712 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002713 // 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 +00002714 // x-direction. We really should continue to walk away from the degeneracy until
2715 // there is a divergence.
2716 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002717 // construct the subtract so we get the correct Direction below
2718 cross = pts[index].fX - pts[next].fX;
2719 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002720 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002721
reed@google.comac8543f2012-01-30 20:51:25 +00002722 if (cross) {
2723 // record our best guess so far
2724 ymax = pts[index].fY;
2725 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002726 }
reed@google.com69a99432012-01-10 18:00:10 +00002727 }
2728 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002729 if (ymaxCross) {
2730 crossToDir(ymaxCross, dir);
2731 fDirection = *dir;
2732 return true;
2733 } else {
2734 return false;
2735 }
reed@google.comac8543f2012-01-30 20:51:25 +00002736}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002737
2738///////////////////////////////////////////////////////////////////////////////
2739
2740static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2741 SkScalar D, SkScalar t) {
2742 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2743}
2744
2745static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2746 SkScalar t) {
2747 SkScalar A = c3 + 3*(c1 - c2) - c0;
2748 SkScalar B = 3*(c2 - c1 - c1 + c0);
2749 SkScalar C = 3*(c1 - c0);
2750 SkScalar D = c0;
2751 return eval_cubic_coeff(A, B, C, D, t);
2752}
2753
2754/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2755 t value such that cubic(t) = target
2756 */
2757static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2758 SkScalar target, SkScalar* t) {
2759 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2760 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002761
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762 SkScalar D = c0 - target;
2763 SkScalar A = c3 + 3*(c1 - c2) - c0;
2764 SkScalar B = 3*(c2 - c1 - c1 + c0);
2765 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002766
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002767 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2768 SkScalar minT = 0;
2769 SkScalar maxT = SK_Scalar1;
2770 SkScalar mid;
2771 int i;
2772 for (i = 0; i < 16; i++) {
2773 mid = SkScalarAve(minT, maxT);
2774 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2775 if (delta < 0) {
2776 minT = mid;
2777 delta = -delta;
2778 } else {
2779 maxT = mid;
2780 }
2781 if (delta < TOLERANCE) {
2782 break;
2783 }
2784 }
2785 *t = mid;
2786 return true;
2787}
2788
2789template <size_t N> static void find_minmax(const SkPoint pts[],
2790 SkScalar* minPtr, SkScalar* maxPtr) {
2791 SkScalar min, max;
2792 min = max = pts[0].fX;
2793 for (size_t i = 1; i < N; ++i) {
2794 min = SkMinScalar(min, pts[i].fX);
2795 max = SkMaxScalar(max, pts[i].fX);
2796 }
2797 *minPtr = min;
2798 *maxPtr = max;
2799}
2800
2801static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2802 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002803
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 int dir = 1;
2805 if (pts[0].fY > pts[3].fY) {
2806 storage[0] = pts[3];
2807 storage[1] = pts[2];
2808 storage[2] = pts[1];
2809 storage[3] = pts[0];
2810 pts = storage;
2811 dir = -1;
2812 }
2813 if (y < pts[0].fY || y >= pts[3].fY) {
2814 return 0;
2815 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 // quickreject or quickaccept
2818 SkScalar min, max;
2819 find_minmax<4>(pts, &min, &max);
2820 if (x < min) {
2821 return 0;
2822 }
2823 if (x > max) {
2824 return dir;
2825 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002826
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 // compute the actual x(t) value
2828 SkScalar t, xt;
2829 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2830 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2831 } else {
2832 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2833 xt = y < mid ? pts[0].fX : pts[3].fX;
2834 }
2835 return xt < x ? dir : 0;
2836}
2837
2838static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2839 SkPoint dst[10];
2840 int n = SkChopCubicAtYExtrema(pts, dst);
2841 int w = 0;
2842 for (int i = 0; i <= n; ++i) {
2843 w += winding_mono_cubic(&dst[i * 3], x, y);
2844 }
2845 return w;
2846}
2847
2848static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2849 SkScalar y0 = pts[0].fY;
2850 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002851
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 int dir = 1;
2853 if (y0 > y2) {
2854 SkTSwap(y0, y2);
2855 dir = -1;
2856 }
2857 if (y < y0 || y >= y2) {
2858 return 0;
2859 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002860
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 // bounds check on X (not required. is it faster?)
2862#if 0
2863 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2864 return 0;
2865 }
2866#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002867
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002868 SkScalar roots[2];
2869 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2870 2 * (pts[1].fY - pts[0].fY),
2871 pts[0].fY - y,
2872 roots);
2873 SkASSERT(n <= 1);
2874 SkScalar xt;
2875 if (0 == n) {
2876 SkScalar mid = SkScalarAve(y0, y2);
2877 // Need [0] and [2] if dir == 1
2878 // and [2] and [0] if dir == -1
2879 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2880 } else {
2881 SkScalar t = roots[0];
2882 SkScalar C = pts[0].fX;
2883 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2884 SkScalar B = 2 * (pts[1].fX - C);
2885 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2886 }
2887 return xt < x ? dir : 0;
2888}
2889
2890static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2891 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2892 if (y0 == y1) {
2893 return true;
2894 }
2895 if (y0 < y1) {
2896 return y1 <= y2;
2897 } else {
2898 return y1 >= y2;
2899 }
2900}
2901
2902static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2903 SkPoint dst[5];
2904 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002905
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002906 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2907 n = SkChopQuadAtYExtrema(pts, dst);
2908 pts = dst;
2909 }
2910 int w = winding_mono_quad(pts, x, y);
2911 if (n > 0) {
2912 w += winding_mono_quad(&pts[2], x, y);
2913 }
2914 return w;
2915}
2916
2917static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2918 SkScalar x0 = pts[0].fX;
2919 SkScalar y0 = pts[0].fY;
2920 SkScalar x1 = pts[1].fX;
2921 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002922
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002924
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002925 int dir = 1;
2926 if (y0 > y1) {
2927 SkTSwap(y0, y1);
2928 dir = -1;
2929 }
2930 if (y < y0 || y >= y1) {
2931 return 0;
2932 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002933
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002934 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2935 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002936
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002937 if (SkScalarSignAsInt(cross) == dir) {
2938 dir = 0;
2939 }
2940 return dir;
2941}
2942
reed@google.com4db592c2013-10-30 17:39:43 +00002943static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2944 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2945}
2946
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002947bool SkPath::contains(SkScalar x, SkScalar y) const {
2948 bool isInverse = this->isInverseFillType();
2949 if (this->isEmpty()) {
2950 return isInverse;
2951 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002952
reed@google.com4db592c2013-10-30 17:39:43 +00002953 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002954 return isInverse;
2955 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002956
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002957 SkPath::Iter iter(*this, true);
2958 bool done = false;
2959 int w = 0;
2960 do {
2961 SkPoint pts[4];
2962 switch (iter.next(pts, false)) {
2963 case SkPath::kMove_Verb:
2964 case SkPath::kClose_Verb:
2965 break;
2966 case SkPath::kLine_Verb:
2967 w += winding_line(pts, x, y);
2968 break;
2969 case SkPath::kQuad_Verb:
2970 w += winding_quad(pts, x, y);
2971 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002972 case SkPath::kConic_Verb:
2973 SkASSERT(0);
2974 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002975 case SkPath::kCubic_Verb:
2976 w += winding_cubic(pts, x, y);
2977 break;
2978 case SkPath::kDone_Verb:
2979 done = true;
2980 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002981 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002982 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002983
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002984 switch (this->getFillType()) {
2985 case SkPath::kEvenOdd_FillType:
2986 case SkPath::kInverseEvenOdd_FillType:
2987 w &= 1;
2988 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002989 default:
2990 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 }
2992 return SkToBool(w);
2993}