blob: a3f5e13d7fa924f29a639ecc9fd6d10fff64a21c [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +00009#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080010#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000011#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070012#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015
16////////////////////////////////////////////////////////////////////////////
17
reed@google.com3563c9e2011-11-14 19:34:57 +000018/**
19 * Path.bounds is defined to be the bounds of all the control points.
20 * If we called bounds.join(r) we would skip r if r was empty, which breaks
21 * our promise. Hence we have a custom joiner that doesn't look at emptiness
22 */
23static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
24 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
25 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
26 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
27 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
28}
29
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000030static bool is_degenerate(const SkPath& path) {
31 SkPath::Iter iter(path, false);
32 SkPoint pts[4];
33 return SkPath::kDone_Verb == iter.next(pts);
34}
35
bsalomon@google.com30c174b2012-11-13 14:36:42 +000036class SkAutoDisableDirectionCheck {
37public:
38 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070039 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000040 }
41
42 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070043 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000044 }
45
46private:
reed026beb52015-06-10 14:23:15 -070047 SkPath* fPath;
48 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000050#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000051
reed@android.com8a1c16f2008-12-17 15:59:43 +000052/* This guy's constructor/destructor bracket a path editing operation. It is
53 used when we know the bounds of the amount we are going to add to the path
54 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000055
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000057 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000058 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000059
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000060 It also notes if the path was originally degenerate, and if so, sets
61 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000062 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000063 */
64class SkAutoPathBoundsUpdate {
65public:
66 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
67 this->init(path);
68 }
69
70 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
71 SkScalar right, SkScalar bottom) {
72 fRect.set(left, top, right, bottom);
73 this->init(path);
74 }
reed@google.comabf15c12011-01-18 20:35:51 +000075
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000077 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
78 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000079 if (fEmpty || fHasValidBounds) {
80 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 }
82 }
reed@google.comabf15c12011-01-18 20:35:51 +000083
reed@android.com8a1c16f2008-12-17 15:59:43 +000084private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000085 SkPath* fPath;
86 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000087 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000088 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000089 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000090
reed@android.com6b82d1a2009-06-03 02:35:01 +000091 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000092 // Cannot use fRect for our bounds unless we know it is sorted
93 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000095 // Mark the path's bounds as dirty if (1) they are, or (2) the path
96 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000097 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000098 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +000099 if (fHasValidBounds && !fEmpty) {
100 joinNoEmptyChecks(&fRect, fPath->getBounds());
101 }
102 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000103 }
104};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000105#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000106
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107////////////////////////////////////////////////////////////////////////////
108
109/*
110 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000111 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
113
114 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000115 1. If we encounter degenerate segments, remove them
116 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
117 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
118 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119*/
120
121////////////////////////////////////////////////////////////////////////////
122
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000123// flag to require a moveTo if we begin with something else, like lineTo etc.
124#define INITIAL_LASTMOVETOINDEX_VALUE ~0
125
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000126SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800127 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000128 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700129 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000130}
131
132void SkPath::resetFields() {
133 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000134 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000135 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000136 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700137 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000138
139 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700140 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000141}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000142
bungeman@google.coma5809a32013-06-21 15:13:34 +0000143SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000144 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145 this->copyFields(that);
146 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000147}
148
149SkPath::~SkPath() {
150 SkDEBUGCODE(this->validate();)
151}
152
bungeman@google.coma5809a32013-06-21 15:13:34 +0000153SkPath& SkPath::operator=(const SkPath& that) {
154 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156 if (this != &that) {
157 fPathRef.reset(SkRef(that.fPathRef.get()));
158 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 }
160 SkDEBUGCODE(this->validate();)
161 return *this;
162}
163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164void SkPath::copyFields(const SkPath& that) {
165 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000166 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700169 // Simulate fFirstDirection = that.fFirstDirection;
170 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700171 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172}
173
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000174bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000175 // note: don't need to look at isConvex or bounds, since just comparing the
176 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000177 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000178 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000179}
180
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700183 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000184 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700187 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
188 uint8_t temp = fFirstDirection;
189 fFirstDirection.store(that.fFirstDirection.load());
190 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700191 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 }
193}
194
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000195static inline bool check_edge_against_rect(const SkPoint& p0,
196 const SkPoint& p1,
197 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700198 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000199 const SkPoint* edgeBegin;
200 SkVector v;
reed026beb52015-06-10 14:23:15 -0700201 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000202 v = p1 - p0;
203 edgeBegin = &p0;
204 } else {
205 v = p0 - p1;
206 edgeBegin = &p1;
207 }
208 if (v.fX || v.fY) {
209 // check the cross product of v with the vec from edgeBegin to each rect corner
210 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
211 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
212 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
213 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
214 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
215 return false;
216 }
217 }
218 return true;
219}
220
221bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
222 // This only handles non-degenerate convex paths currently.
223 if (kConvex_Convexity != this->getConvexity()) {
224 return false;
225 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000226
reed026beb52015-06-10 14:23:15 -0700227 SkPathPriv::FirstDirection direction;
228 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000229 return false;
230 }
231
232 SkPoint firstPt;
233 SkPoint prevPt;
234 RawIter iter(*this);
235 SkPath::Verb verb;
236 SkPoint pts[4];
237 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000238 SkDEBUGCODE(int segmentCount = 0;)
239 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000240
241 while ((verb = iter.next(pts)) != kDone_Verb) {
242 int nextPt = -1;
243 switch (verb) {
244 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000245 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246 SkDEBUGCODE(++moveCnt);
247 firstPt = prevPt = pts[0];
248 break;
249 case kLine_Verb:
250 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000251 SkASSERT(moveCnt && !closeCount);
252 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 break;
254 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000255 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000256 SkASSERT(moveCnt && !closeCount);
257 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 nextPt = 2;
259 break;
260 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000261 SkASSERT(moveCnt && !closeCount);
262 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 nextPt = 3;
264 break;
265 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000266 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000267 break;
268 default:
269 SkDEBUGFAIL("unknown verb");
270 }
271 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800272 if (SkPath::kConic_Verb == verb) {
273 SkConic orig;
274 orig.set(pts, iter.conicWeight());
275 SkPoint quadPts[5];
276 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
277 SK_ALWAYSBREAK(2 == count);
278
279 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
280 return false;
281 }
282 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
283 return false;
284 }
285 } else {
286 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
287 return false;
288 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 }
290 prevPt = pts[nextPt];
291 }
292 }
293
294 return check_edge_against_rect(prevPt, firstPt, rect, direction);
295}
296
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000297uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000298 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800299#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000300 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
301 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
302#endif
303 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000304}
djsollen@google.come63793a2012-03-21 15:39:03 +0000305
reed@android.com8a1c16f2008-12-17 15:59:43 +0000306void SkPath::reset() {
307 SkDEBUGCODE(this->validate();)
308
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000309 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000310 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311}
312
313void SkPath::rewind() {
314 SkDEBUGCODE(this->validate();)
315
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000316 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000317 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318}
319
reed@google.com7e6c4d12012-05-10 14:05:43 +0000320bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000321 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000322
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000323 if (2 == verbCount) {
324 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
325 if (kLine_Verb == fPathRef->atVerb(1)) {
326 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000327 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000328 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000329 line[0] = pts[0];
330 line[1] = pts[1];
331 }
332 return true;
333 }
334 }
335 return false;
336}
337
caryclark@google.comf1316942011-07-26 19:54:45 +0000338/*
339 Determines if path is a rect by keeping track of changes in direction
340 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000341
caryclark@google.comf1316942011-07-26 19:54:45 +0000342 The direction is computed such that:
343 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000344 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000345 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000346 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000347
caryclark@google.comf1316942011-07-26 19:54:45 +0000348A rectangle cycles up/right/down/left or up/left/down/right.
349
350The test fails if:
351 The path is closed, and followed by a line.
352 A second move creates a new endpoint.
353 A diagonal line is parsed.
354 There's more than four changes of direction.
355 There's a discontinuity on the line (e.g., a move in the middle)
356 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000357 The path contains a quadratic or cubic.
358 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000359 *The rectangle doesn't complete a cycle.
360 *The final point isn't equal to the first point.
361
362 *These last two conditions we relax if we have a 3-edge path that would
363 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000364
caryclark@google.comf1316942011-07-26 19:54:45 +0000365It's OK if the path has:
366 Several colinear line segments composing a rectangle side.
367 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000368
caryclark@google.comf1316942011-07-26 19:54:45 +0000369The direction takes advantage of the corners found since opposite sides
370must travel in opposite directions.
371
372FIXME: Allow colinear quads and cubics to be treated like lines.
373FIXME: If the API passes fill-only, return true if the filled stroke
374 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000375
376 first,last,next direction state-machine:
377 0x1 is set if the segment is horizontal
378 0x2 is set if the segment is moving to the right or down
379 thus:
380 two directions are opposites iff (dirA ^ dirB) == 0x2
381 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000382
caryclark@google.comf1316942011-07-26 19:54:45 +0000383 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000384static int rect_make_dir(SkScalar dx, SkScalar dy) {
385 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
386}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000387bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
388 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000389 int corners = 0;
390 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000391 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700392 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000393 first.set(0, 0);
394 last.set(0, 0);
395 int firstDirection = 0;
396 int lastDirection = 0;
397 int nextDirection = 0;
398 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000399 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700400 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000401 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000402 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700403 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
404 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000406 savePts = pts;
407 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700409 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000410 case kLine_Verb: {
411 SkScalar left = last.fX;
412 SkScalar top = last.fY;
413 SkScalar right = pts->fX;
414 SkScalar bottom = pts->fY;
415 ++pts;
416 if (left != right && top != bottom) {
417 return false; // diagonal
418 }
419 if (left == right && top == bottom) {
420 break; // single point on side OK
421 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000422 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000423 if (0 == corners) {
424 firstDirection = nextDirection;
425 first = last;
426 last = pts[-1];
427 corners = 1;
428 closedOrMoved = false;
429 break;
430 }
431 if (closedOrMoved) {
432 return false; // closed followed by a line
433 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000434 if (autoClose && nextDirection == firstDirection) {
435 break; // colinear with first
436 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000437 closedOrMoved = autoClose;
438 if (lastDirection != nextDirection) {
439 if (++corners > 4) {
440 return false; // too many direction changes
441 }
442 }
443 last = pts[-1];
444 if (lastDirection == nextDirection) {
445 break; // colinear segment
446 }
447 // Possible values for corners are 2, 3, and 4.
448 // When corners == 3, nextDirection opposes firstDirection.
449 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000450 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
452 if ((directionCycle ^ turn) != nextDirection) {
453 return false; // direction didn't follow cycle
454 }
455 break;
456 }
457 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000458 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 case kCubic_Verb:
460 return false; // quadratic, cubic not allowed
461 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700462 if (allowPartial && !autoClose && firstDirection) {
463 insertClose = true;
464 *currVerb -= 1; // try move again afterwards
465 goto addMissingClose;
466 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000467 last = *pts++;
468 closedOrMoved = true;
469 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000470 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000471 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000472 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000474 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700476addMissingClose:
477 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000478 }
479 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000480 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000481 if (!result) {
482 // check if we are just an incomplete rectangle, in which case we can
483 // return true, but not claim to be closed.
484 // e.g.
485 // 3 sided rectangle
486 // 4 sided but the last edge is not long enough to reach the start
487 //
488 SkScalar closeX = first.x() - last.x();
489 SkScalar closeY = first.y() - last.y();
490 if (closeX && closeY) {
491 return false; // we're diagonal, abort (can we ever reach this?)
492 }
493 int closeDirection = rect_make_dir(closeX, closeY);
494 // make sure the close-segment doesn't double-back on itself
495 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
496 result = true;
497 autoClose = false; // we are not closed
498 }
499 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000500 if (savePts) {
501 *ptsPtr = savePts;
502 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 if (result && isClosed) {
504 *isClosed = autoClose;
505 }
506 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000507 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000508 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000509 return result;
510}
511
robertphillips4f662e62014-12-29 14:06:51 -0800512bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000513 SkDEBUGCODE(this->validate();)
514 int currVerb = 0;
515 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800516 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800517 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800518 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800520 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800521 int32_t num = SkToS32(pts - first);
522 if (num) {
523 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800524 } else {
525 // 'pts' isn't updated for open rects
526 *rect = this->getBounds();
527 }
528 }
529 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000530}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000531
caryclark95bc5f32015-04-08 08:34:15 -0700532bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000533 SkDEBUGCODE(this->validate();)
534 int currVerb = 0;
535 const SkPoint* pts = fPathRef->points();
536 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000537 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700538 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000539 return false;
540 }
541 const SkPoint* last = pts;
542 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700543 bool isClosed;
544 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000545 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700546 if (!isClosed) {
547 pts = fPathRef->points() + fPathRef->countPoints();
548 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000549 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 if (testRects[0].contains(testRects[1])) {
551 if (rects) {
552 rects[0] = testRects[0];
553 rects[1] = testRects[1];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[0];
557 dirs[1] = testDirs[1];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 if (testRects[1].contains(testRects[0])) {
562 if (rects) {
563 rects[0] = testRects[1];
564 rects[1] = testRects[0];
565 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000566 if (dirs) {
567 dirs[0] = testDirs[1];
568 dirs[1] = testDirs[0];
569 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000570 return true;
571 }
572 }
573 return false;
574}
575
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000576int SkPath::countPoints() const {
577 return fPathRef->countPoints();
578}
579
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000580int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000581 SkDEBUGCODE(this->validate();)
582
583 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000584 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585 int count = SkMin32(max, fPathRef->countPoints());
586 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
587 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000588}
589
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000590SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000591 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
592 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000593 }
594 return SkPoint::Make(0, 0);
595}
596
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000597int SkPath::countVerbs() const {
598 return fPathRef->countVerbs();
599}
600
601static inline void copy_verbs_reverse(uint8_t* inorderDst,
602 const uint8_t* reversedSrc,
603 int count) {
604 for (int i = 0; i < count; ++i) {
605 inorderDst[i] = reversedSrc[~i];
606 }
607}
608
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000609int SkPath::getVerbs(uint8_t dst[], int max) const {
610 SkDEBUGCODE(this->validate();)
611
612 SkASSERT(max >= 0);
613 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 int count = SkMin32(max, fPathRef->countVerbs());
615 copy_verbs_reverse(dst, fPathRef->verbs(), count);
616 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000617}
618
reed@google.com294dd7b2011-10-11 11:58:32 +0000619bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000620 SkDEBUGCODE(this->validate();)
621
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000622 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000623 if (count > 0) {
624 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000625 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000627 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000629 if (lastPt) {
630 lastPt->set(0, 0);
631 }
632 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633}
634
caryclarkaec25102015-04-29 08:28:30 -0700635void SkPath::setPt(int index, SkScalar x, SkScalar y) {
636 SkDEBUGCODE(this->validate();)
637
638 int count = fPathRef->countPoints();
639 if (count <= index) {
640 return;
641 } else {
642 SkPathRef::Editor ed(&fPathRef);
643 ed.atPoint(index)->set(x, y);
644 }
645}
646
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647void SkPath::setLastPt(SkScalar x, SkScalar y) {
648 SkDEBUGCODE(this->validate();)
649
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000651 if (count == 0) {
652 this->moveTo(x, y);
653 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 SkPathRef::Editor ed(&fPathRef);
655 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 }
657}
658
reed@google.com04863fa2011-05-15 04:08:24 +0000659void SkPath::setConvexity(Convexity c) {
660 if (fConvexity != c) {
661 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000662 }
663}
664
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665//////////////////////////////////////////////////////////////////////////////
666// Construction methods
667
reed026beb52015-06-10 14:23:15 -0700668#define DIRTY_AFTER_EDIT \
669 do { \
670 fConvexity = kUnknown_Convexity; \
671 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000672 } while (0)
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674void SkPath::incReserve(U16CPU inc) {
675 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 SkDEBUGCODE(this->validate();)
678}
679
680void SkPath::moveTo(SkScalar x, SkScalar y) {
681 SkDEBUGCODE(this->validate();)
682
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000685 // remember our index
686 fLastMoveToIndex = fPathRef->countPoints();
687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700689
690 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691}
692
693void SkPath::rMoveTo(SkScalar x, SkScalar y) {
694 SkPoint pt;
695 this->getLastPt(&pt);
696 this->moveTo(pt.fX + x, pt.fY + y);
697}
698
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000699void SkPath::injectMoveToIfNeeded() {
700 if (fLastMoveToIndex < 0) {
701 SkScalar x, y;
702 if (fPathRef->countVerbs() == 0) {
703 x = y = 0;
704 } else {
705 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
706 x = pt.fX;
707 y = pt.fY;
708 }
709 this->moveTo(x, y);
710 }
711}
712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713void SkPath::lineTo(SkScalar x, SkScalar y) {
714 SkDEBUGCODE(this->validate();)
715
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000716 this->injectMoveToIfNeeded();
717
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000718 SkPathRef::Editor ed(&fPathRef);
719 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000720
reed@google.comb54455e2011-05-16 14:16:04 +0000721 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722}
723
724void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000725 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726 SkPoint pt;
727 this->getLastPt(&pt);
728 this->lineTo(pt.fX + x, pt.fY + y);
729}
730
731void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
732 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000733
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000734 this->injectMoveToIfNeeded();
735
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000736 SkPathRef::Editor ed(&fPathRef);
737 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738 pts[0].set(x1, y1);
739 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000740
reed@google.comb54455e2011-05-16 14:16:04 +0000741 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742}
743
744void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000745 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746 SkPoint pt;
747 this->getLastPt(&pt);
748 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
749}
750
reed@google.com277c3f82013-05-31 15:17:50 +0000751void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
752 SkScalar w) {
753 // check for <= 0 or NaN with this test
754 if (!(w > 0)) {
755 this->lineTo(x2, y2);
756 } else if (!SkScalarIsFinite(w)) {
757 this->lineTo(x1, y1);
758 this->lineTo(x2, y2);
759 } else if (SK_Scalar1 == w) {
760 this->quadTo(x1, y1, x2, y2);
761 } else {
762 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000763
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000764 this->injectMoveToIfNeeded();
765
reed@google.com277c3f82013-05-31 15:17:50 +0000766 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000767 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000768 pts[0].set(x1, y1);
769 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000770
reed@google.com277c3f82013-05-31 15:17:50 +0000771 DIRTY_AFTER_EDIT;
772 }
773}
774
775void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
776 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000777 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000778 SkPoint pt;
779 this->getLastPt(&pt);
780 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
781}
782
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
784 SkScalar x3, SkScalar y3) {
785 SkDEBUGCODE(this->validate();)
786
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000787 this->injectMoveToIfNeeded();
788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000789 SkPathRef::Editor ed(&fPathRef);
790 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 pts[0].set(x1, y1);
792 pts[1].set(x2, y2);
793 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794
reed@google.comb54455e2011-05-16 14:16:04 +0000795 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796}
797
798void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
799 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000800 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 SkPoint pt;
802 this->getLastPt(&pt);
803 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
804 pt.fX + x3, pt.fY + y3);
805}
806
807void SkPath::close() {
808 SkDEBUGCODE(this->validate();)
809
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000810 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000811 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000812 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 case kLine_Verb:
814 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000815 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000817 case kMove_Verb: {
818 SkPathRef::Editor ed(&fPathRef);
819 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 }
reed@google.com277c3f82013-05-31 15:17:50 +0000822 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000823 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000824 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000825 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000826 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000827 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 }
829 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000830
831 // signal that we need a moveTo to follow us (unless we're done)
832#if 0
833 if (fLastMoveToIndex >= 0) {
834 fLastMoveToIndex = ~fLastMoveToIndex;
835 }
836#else
837 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
838#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839}
840
841///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000842
fmalitac08d53e2015-11-17 09:53:29 -0800843namespace {
844
845template <unsigned N>
846class PointIterator {
847public:
848 PointIterator(SkPath::Direction dir, unsigned startIndex)
849 : fCurrent(startIndex % N)
850 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
851
852 const SkPoint& current() const {
853 SkASSERT(fCurrent < N);
854 return fPts[fCurrent];
855 }
856
857 const SkPoint& next() {
858 fCurrent = (fCurrent + fAdvance) % N;
859 return this->current();
860 }
861
862protected:
863 SkPoint fPts[N];
864
865private:
866 unsigned fCurrent;
867 unsigned fAdvance;
868};
869
870class RectPointIterator : public PointIterator<4> {
871public:
872 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
873 : PointIterator(dir, startIndex) {
874
875 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
876 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
877 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
878 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
879 }
880};
881
882class OvalPointIterator : public PointIterator<4> {
883public:
884 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
885 : PointIterator(dir, startIndex) {
886
887 const SkScalar cx = oval.centerX();
888 const SkScalar cy = oval.centerY();
889
890 fPts[0] = SkPoint::Make(cx, oval.fTop);
891 fPts[1] = SkPoint::Make(oval.fRight, cy);
892 fPts[2] = SkPoint::Make(cx, oval.fBottom);
893 fPts[3] = SkPoint::Make(oval.fLeft, cy);
894 }
895};
896
897class RRectPointIterator : public PointIterator<8> {
898public:
899 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
900 : PointIterator(dir, startIndex) {
901
902 const SkRect& bounds = rrect.getBounds();
903 const SkScalar L = bounds.fLeft;
904 const SkScalar T = bounds.fTop;
905 const SkScalar R = bounds.fRight;
906 const SkScalar B = bounds.fBottom;
907
908 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
909 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
910 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
911 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
912 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
913 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
914 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
915 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
916 }
917};
918
919} // anonymous namespace
920
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000921static void assert_known_direction(int dir) {
922 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
923}
924
reed@android.com8a1c16f2008-12-17 15:59:43 +0000925void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800926 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000927}
928
929void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
930 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800931 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
932}
933
934void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000935 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700936 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800937 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000938 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800939 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000940
fmalitac08d53e2015-11-17 09:53:29 -0800941 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942
fmalitac08d53e2015-11-17 09:53:29 -0800943 const int kVerbs = 5; // moveTo + 3x lineTo + close
944 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000945
fmalitac08d53e2015-11-17 09:53:29 -0800946 RectPointIterator iter(rect, dir, startIndex);
947
948 this->moveTo(iter.current());
949 this->lineTo(iter.next());
950 this->lineTo(iter.next());
951 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800953
954 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000955}
956
reed@google.com744faba2012-05-29 19:54:52 +0000957void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
958 SkDEBUGCODE(this->validate();)
959 if (count <= 0) {
960 return;
961 }
962
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000963 fLastMoveToIndex = fPathRef->countPoints();
964
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000965 // +close makes room for the extra kClose_Verb
966 SkPathRef::Editor ed(&fPathRef, count+close, count);
967
968 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000969 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000970 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
971 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000972 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000973
reed@google.com744faba2012-05-29 19:54:52 +0000974 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000975 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800976 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000977 }
978
reed@google.com744faba2012-05-29 19:54:52 +0000979 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000980 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000981}
982
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000983#include "SkGeometry.h"
984
reedf90ea012015-01-29 12:03:58 -0800985static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
986 SkPoint* pt) {
987 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000988 // Chrome uses this path to move into and out of ovals. If not
989 // treated as a special case the moves can distort the oval's
990 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800991 pt->set(oval.fRight, oval.centerY());
992 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000993 } else if (0 == oval.width() && 0 == oval.height()) {
994 // Chrome will sometimes create 0 radius round rects. Having degenerate
995 // quad segments in the path prevents the path from being recognized as
996 // a rect.
997 // TODO: optimizing the case where only one of width or height is zero
998 // should also be considered. This case, however, doesn't seem to be
999 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001000 pt->set(oval.fRight, oval.fTop);
1001 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001002 }
reedf90ea012015-01-29 12:03:58 -08001003 return false;
1004}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001005
reedd5d27d92015-02-09 13:54:43 -08001006// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1007//
1008static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1009 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1010 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1011 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001012
1013 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001014 loss in radians-conversion and/or sin/cos, we may end up with coincident
1015 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1016 of drawing a nearly complete circle (good).
1017 e.g. canvas.drawArc(0, 359.99, ...)
1018 -vs- canvas.drawArc(0, 359.9, ...)
1019 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001020 */
reedd5d27d92015-02-09 13:54:43 -08001021 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001022 SkScalar sw = SkScalarAbs(sweepAngle);
1023 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1024 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1025 // make a guess at a tiny angle (in radians) to tweak by
1026 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1027 // not sure how much will be enough, so we use a loop
1028 do {
1029 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001030 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1031 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001032 }
1033 }
reedd5d27d92015-02-09 13:54:43 -08001034 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1035}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001036
reed9e779d42015-02-17 11:43:14 -08001037/**
1038 * If this returns 0, then the caller should just line-to the singlePt, else it should
1039 * ignore singlePt and append the specified number of conics.
1040 */
reedd5d27d92015-02-09 13:54:43 -08001041static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001042 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1043 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001044 SkMatrix matrix;
1045
1046 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1047 matrix.postTranslate(oval.centerX(), oval.centerY());
1048
reed9e779d42015-02-17 11:43:14 -08001049 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1050 if (0 == count) {
1051 matrix.mapXY(start.x(), start.y(), singlePt);
1052 }
1053 return count;
reedd5d27d92015-02-09 13:54:43 -08001054}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001056void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001057 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001058 SkRRect rrect;
1059 rrect.setRectRadii(rect, (const SkVector*) radii);
1060 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001061}
1062
reed@google.com4ed0fb72012-12-12 20:48:18 +00001063void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001064 // legacy start indices: 6 (CW) and 7(CCW)
1065 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1066}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001067
fmalitac08d53e2015-11-17 09:53:29 -08001068void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1069 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001070
fmalitac08d53e2015-11-17 09:53:29 -08001071 if (rrect.isEmpty()) {
1072 return;
reed1b28a3a2014-12-17 14:42:09 -08001073 }
fmalitac08d53e2015-11-17 09:53:29 -08001074
1075 const SkRect& bounds = rrect.getBounds();
1076
1077 if (rrect.isRect()) {
1078 // degenerate(rect) => radii points are collapsing
1079 this->addRect(bounds, dir, (startIndex + 1) / 2);
1080 } else if (rrect.isOval()) {
1081 // degenerate(oval) => line points are collapsing
1082 this->addOval(bounds, dir, startIndex / 2);
1083 } else {
1084 fFirstDirection = this->hasOnlyMoveTos() ?
1085 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1086
1087 SkAutoPathBoundsUpdate apbu(this, bounds);
1088 SkAutoDisableDirectionCheck addc(this);
1089
1090 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1091 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1092 const SkScalar weight = SK_ScalarRoot2Over2;
1093
1094 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1095 const int kVerbs = startsWithConic
1096 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1097 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1098 this->incReserve(kVerbs);
1099
1100 RRectPointIterator rrectIter(rrect, dir, startIndex);
1101 // Corner iterator indices follow the collapsed radii model,
1102 // adjusted such that the start pt is "behind" the radii start pt.
1103 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1104 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1105
1106 this->moveTo(rrectIter.current());
1107 if (startsWithConic) {
1108 for (unsigned i = 0; i < 3; ++i) {
1109 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1110 this->lineTo(rrectIter.next());
1111 }
1112 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1113 // final lineTo handled by close().
1114 } else {
1115 for (unsigned i = 0; i < 4; ++i) {
1116 this->lineTo(rrectIter.next());
1117 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1118 }
1119 }
1120 this->close();
1121
1122 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1123 }
1124
1125 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001126}
1127
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001128bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001129 int count = fPathRef->countVerbs();
1130 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1131 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001132 if (*verbs == kLine_Verb ||
1133 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001134 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001135 *verbs == kCubic_Verb) {
1136 return false;
1137 }
1138 ++verbs;
1139 }
1140 return true;
1141}
1142
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001143void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1144 Direction dir) {
1145 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001146
humper@google.com75e3ca12013-04-08 21:44:11 +00001147 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001148 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001149 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001150 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001151 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1152 return;
1153 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001154
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001155 SkRRect rrect;
1156 rrect.setRectXY(rect, rx, ry);
1157 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001158}
1159
reed@android.com8a1c16f2008-12-17 15:59:43 +00001160void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001161 // legacy start index: 1
1162 this->addOval(oval, dir, 1);
1163}
1164
1165void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001166 assert_known_direction(dir);
1167
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001168 /* If addOval() is called after previous moveTo(),
1169 this path is still marked as an oval. This is used to
1170 fit into WebKit's calling sequences.
1171 We can't simply check isEmpty() in this case, as additional
1172 moveTo() would mark the path non empty.
1173 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001174 bool isOval = hasOnlyMoveTos();
1175 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001176 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001177 } else {
reed026beb52015-06-10 14:23:15 -07001178 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001179 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001180
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001181 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001182 SkAutoPathBoundsUpdate apbu(this, oval);
1183
fmalitac08d53e2015-11-17 09:53:29 -08001184 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1185 const int kVerbs = 6; // moveTo + 4x conicTo + close
1186 this->incReserve(kVerbs);
1187
1188 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1189 // The corner iterator pts are tracking "behind" the oval/radii pts.
1190 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001191 const SkScalar weight = SK_ScalarRoot2Over2;
1192
fmalitac08d53e2015-11-17 09:53:29 -08001193 this->moveTo(ovalIter.current());
1194 for (unsigned i = 0; i < 4; ++i) {
1195 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001196 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001198
fmalitac08d53e2015-11-17 09:53:29 -08001199 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1200
robertphillips@google.com466310d2013-12-03 16:43:54 +00001201 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001202
robertphillips@google.com466310d2013-12-03 16:43:54 +00001203 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001204}
1205
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1207 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001208 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 }
1210}
1211
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1213 bool forceMoveTo) {
1214 if (oval.width() < 0 || oval.height() < 0) {
1215 return;
1216 }
1217
reedf90ea012015-01-29 12:03:58 -08001218 if (fPathRef->countVerbs() == 0) {
1219 forceMoveTo = true;
1220 }
1221
1222 SkPoint lonePt;
1223 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1224 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1225 return;
1226 }
1227
reedd5d27d92015-02-09 13:54:43 -08001228 SkVector startV, stopV;
1229 SkRotationDirection dir;
1230 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1231
reed9e779d42015-02-17 11:43:14 -08001232 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001233 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001234 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001235 if (count) {
1236 this->incReserve(count * 2 + 1);
1237 const SkPoint& pt = conics[0].fPts[0];
1238 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1239 for (int i = 0; i < count; ++i) {
1240 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1241 }
reed9e779d42015-02-17 11:43:14 -08001242 } else {
1243 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001244 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001245}
1246
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001247void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 if (oval.isEmpty() || 0 == sweepAngle) {
1249 return;
1250 }
1251
1252 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1253
1254 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1255 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001256 } else {
1257 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259}
1260
1261/*
1262 Need to handle the case when the angle is sharp, and our computed end-points
1263 for the arc go behind pt1 and/or p2...
1264*/
reedc7789042015-01-29 12:59:11 -08001265void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001266 if (radius == 0) {
1267 this->lineTo(x1, y1);
1268 return;
1269 }
1270
1271 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 // need to know our prev pt so we can construct tangent vectors
1274 {
1275 SkPoint start;
1276 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001277 // Handle degenerate cases by adding a line to the first point and
1278 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 before.setNormalize(x1 - start.fX, y1 - start.fY);
1280 after.setNormalize(x2 - x1, y2 - y1);
1281 }
reed@google.comabf15c12011-01-18 20:35:51 +00001282
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283 SkScalar cosh = SkPoint::DotProduct(before, after);
1284 SkScalar sinh = SkPoint::CrossProduct(before, after);
1285
1286 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001287 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 return;
1289 }
reed@google.comabf15c12011-01-18 20:35:51 +00001290
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1292 if (dist < 0) {
1293 dist = -dist;
1294 }
1295
1296 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1297 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1298 SkRotationDirection arcDir;
1299
1300 // now turn before/after into normals
1301 if (sinh > 0) {
1302 before.rotateCCW();
1303 after.rotateCCW();
1304 arcDir = kCW_SkRotationDirection;
1305 } else {
1306 before.rotateCW();
1307 after.rotateCW();
1308 arcDir = kCCW_SkRotationDirection;
1309 }
1310
1311 SkMatrix matrix;
1312 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001313
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 matrix.setScale(radius, radius);
1315 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1316 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001317
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001319
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 this->incReserve(count);
1321 // [xx,yy] == pts[0]
1322 this->lineTo(xx, yy);
1323 for (int i = 1; i < count; i += 2) {
1324 this->quadTo(pts[i], pts[i+1]);
1325 }
1326}
1327
1328///////////////////////////////////////////////////////////////////////////////
1329
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001330void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 SkMatrix matrix;
1332
1333 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001334 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001335}
1336
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001337void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001338 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001340 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 SkPoint pts[4];
1342 Verb verb;
1343
1344 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001345 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001346 while ((verb = iter.next(pts)) != kDone_Verb) {
1347 switch (verb) {
1348 case kMove_Verb:
1349 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001350 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1351 injectMoveToIfNeeded(); // In case last contour is closed
1352 this->lineTo(pts[0]);
1353 } else {
1354 this->moveTo(pts[0]);
1355 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356 break;
1357 case kLine_Verb:
1358 proc(matrix, &pts[1], &pts[1], 1);
1359 this->lineTo(pts[1]);
1360 break;
1361 case kQuad_Verb:
1362 proc(matrix, &pts[1], &pts[1], 2);
1363 this->quadTo(pts[1], pts[2]);
1364 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001365 case kConic_Verb:
1366 proc(matrix, &pts[1], &pts[1], 2);
1367 this->conicTo(pts[1], pts[2], iter.conicWeight());
1368 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 case kCubic_Verb:
1370 proc(matrix, &pts[1], &pts[1], 3);
1371 this->cubicTo(pts[1], pts[2], pts[3]);
1372 break;
1373 case kClose_Verb:
1374 this->close();
1375 break;
1376 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001377 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001379 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 }
1381}
1382
1383///////////////////////////////////////////////////////////////////////////////
1384
reed@google.com277c3f82013-05-31 15:17:50 +00001385static int pts_in_verb(unsigned verb) {
1386 static const uint8_t gPtsInVerb[] = {
1387 1, // kMove
1388 1, // kLine
1389 2, // kQuad
1390 2, // kConic
1391 3, // kCubic
1392 0, // kClose
1393 0 // kDone
1394 };
1395
1396 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1397 return gPtsInVerb[verb];
1398}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400// ignore the last point of the 1st contour
1401void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001402 int i, vcount = path.fPathRef->countVerbs();
1403 // exit early if the path is empty, or just has a moveTo.
1404 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 return;
1406 }
1407
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001408 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001409
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001410 const uint8_t* verbs = path.fPathRef->verbs();
1411 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001412 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001414 SkASSERT(verbs[~0] == kMove_Verb);
1415 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001416 unsigned v = verbs[~i];
1417 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 if (n == 0) {
1419 break;
1420 }
1421 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001422 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 }
1424
1425 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001426 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 case kLine_Verb:
1428 this->lineTo(pts[-1].fX, pts[-1].fY);
1429 break;
1430 case kQuad_Verb:
1431 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1432 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001433 case kConic_Verb:
1434 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1435 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 case kCubic_Verb:
1437 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1438 pts[-3].fX, pts[-3].fY);
1439 break;
1440 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001441 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 break;
1443 }
reed@google.com277c3f82013-05-31 15:17:50 +00001444 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445 }
1446}
1447
reed@google.com63d73742012-01-10 15:33:12 +00001448void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001449 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001450
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001451 const SkPoint* pts = src.fPathRef->pointsEnd();
1452 // we will iterator through src's verbs backwards
1453 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1454 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001455 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001456
1457 bool needMove = true;
1458 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001459 while (verbs < verbsEnd) {
1460 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001461 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001462
1463 if (needMove) {
1464 --pts;
1465 this->moveTo(pts->fX, pts->fY);
1466 needMove = false;
1467 }
1468 pts -= n;
1469 switch (v) {
1470 case kMove_Verb:
1471 if (needClose) {
1472 this->close();
1473 needClose = false;
1474 }
1475 needMove = true;
1476 pts += 1; // so we see the point in "if (needMove)" above
1477 break;
1478 case kLine_Verb:
1479 this->lineTo(pts[0]);
1480 break;
1481 case kQuad_Verb:
1482 this->quadTo(pts[1], pts[0]);
1483 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001484 case kConic_Verb:
1485 this->conicTo(pts[1], pts[0], *--conicWeights);
1486 break;
reed@google.com63d73742012-01-10 15:33:12 +00001487 case kCubic_Verb:
1488 this->cubicTo(pts[2], pts[1], pts[0]);
1489 break;
1490 case kClose_Verb:
1491 needClose = true;
1492 break;
1493 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001494 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001495 }
1496 }
1497}
1498
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499///////////////////////////////////////////////////////////////////////////////
1500
1501void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1502 SkMatrix matrix;
1503
1504 matrix.setTranslate(dx, dy);
1505 this->transform(matrix, dst);
1506}
1507
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1509 int level = 2) {
1510 if (--level >= 0) {
1511 SkPoint tmp[7];
1512
1513 SkChopCubicAtHalf(pts, tmp);
1514 subdivide_cubic_to(path, &tmp[0], level);
1515 subdivide_cubic_to(path, &tmp[3], level);
1516 } else {
1517 path->cubicTo(pts[1], pts[2], pts[3]);
1518 }
1519}
1520
1521void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1522 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001523 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 dst = (SkPath*)this;
1525 }
1526
tomhudson@google.com8d430182011-06-06 19:11:19 +00001527 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 SkPath tmp;
1529 tmp.fFillType = fFillType;
1530
1531 SkPath::Iter iter(*this, false);
1532 SkPoint pts[4];
1533 SkPath::Verb verb;
1534
reed@google.com4a3b7142012-05-16 17:16:46 +00001535 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 switch (verb) {
1537 case kMove_Verb:
1538 tmp.moveTo(pts[0]);
1539 break;
1540 case kLine_Verb:
1541 tmp.lineTo(pts[1]);
1542 break;
1543 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001544 // promote the quad to a conic
1545 tmp.conicTo(pts[1], pts[2],
1546 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001548 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001549 tmp.conicTo(pts[1], pts[2],
1550 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001551 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 case kCubic_Verb:
1553 subdivide_cubic_to(&tmp, pts);
1554 break;
1555 case kClose_Verb:
1556 tmp.close();
1557 break;
1558 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001559 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 break;
1561 }
1562 }
1563
1564 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 SkPathRef::Editor ed(&dst->fPathRef);
1566 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001567 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1570
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001573 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001574 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001576
reed026beb52015-06-10 14:23:15 -07001577 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1578 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001579 } else {
1580 SkScalar det2x2 =
1581 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1582 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1583 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001584 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1585 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001586 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001587 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001588 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001589 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001590 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001591 }
1592 }
1593
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 SkDEBUGCODE(dst->validate();)
1595 }
1596}
1597
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598///////////////////////////////////////////////////////////////////////////////
1599///////////////////////////////////////////////////////////////////////////////
1600
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001601enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001602 kEmptyContour_SegmentState, // The current contour is empty. We may be
1603 // starting processing or we may have just
1604 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001605 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1606 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1607 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608};
1609
1610SkPath::Iter::Iter() {
1611#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001612 fPts = nullptr;
1613 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001614 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001615 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001616 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617#endif
1618 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001619 fVerbs = nullptr;
1620 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 fNeedClose = false;
1622}
1623
1624SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1625 this->setPath(path, forceClose);
1626}
1627
1628void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001629 fPts = path.fPathRef->points();
1630 fVerbs = path.fPathRef->verbs();
1631 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001632 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001633 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001634 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 fForceClose = SkToU8(forceClose);
1636 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001637 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638}
1639
1640bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001641 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642 return false;
1643 }
1644 if (fForceClose) {
1645 return true;
1646 }
1647
1648 const uint8_t* verbs = fVerbs;
1649 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001650
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001651 if (kMove_Verb == *(verbs - 1)) {
1652 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 }
1654
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001655 while (verbs > stop) {
1656 // verbs points one beyond the current verb, decrement first.
1657 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 if (kMove_Verb == v) {
1659 break;
1660 }
1661 if (kClose_Verb == v) {
1662 return true;
1663 }
1664 }
1665 return false;
1666}
1667
1668SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001669 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001671 // A special case: if both points are NaN, SkPoint::operation== returns
1672 // false, but the iterator expects that they are treated as the same.
1673 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001674 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1675 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001676 return kClose_Verb;
1677 }
1678
reed@google.com9e25dbf2012-05-15 17:05:38 +00001679 pts[0] = fLastPt;
1680 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 fLastPt = fMoveTo;
1682 fCloseLine = true;
1683 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001684 } else {
1685 pts[0] = fMoveTo;
1686 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688}
1689
reed@google.com9e25dbf2012-05-15 17:05:38 +00001690const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001691 if (fSegmentState == kAfterMove_SegmentState) {
1692 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001693 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001694 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001696 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1697 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001698 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700}
1701
caryclarke8c56662015-07-14 11:19:26 -07001702void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001703 // We need to step over anything that will not move the current draw point
1704 // forward before the next move is seen
1705 const uint8_t* lastMoveVerb = 0;
1706 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001707 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708 SkPoint lastPt = fLastPt;
1709 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001710 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001711 switch (verb) {
1712 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001713 // Keep a record of this most recent move
1714 lastMoveVerb = fVerbs;
1715 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001716 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001717 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001718 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001719 fPts++;
1720 break;
1721
1722 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001723 // A close when we are in a segment is always valid except when it
1724 // follows a move which follows a segment.
1725 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726 return;
1727 }
1728 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001729 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 break;
1731
1732 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001733 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001734 if (lastMoveVerb) {
1735 fVerbs = lastMoveVerb;
1736 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001737 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 return;
1739 }
1740 return;
1741 }
1742 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001743 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001744 fPts++;
1745 break;
1746
reed@google.com277c3f82013-05-31 15:17:50 +00001747 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001748 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001749 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001750 if (lastMoveVerb) {
1751 fVerbs = lastMoveVerb;
1752 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001753 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001754 return;
1755 }
1756 return;
1757 }
1758 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001759 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001760 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001761 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001762 break;
1763
1764 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001765 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001766 if (lastMoveVerb) {
1767 fVerbs = lastMoveVerb;
1768 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001769 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001770 return;
1771 }
1772 return;
1773 }
1774 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001775 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001776 fPts += 3;
1777 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001778
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001779 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001780 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001781 }
1782 }
1783}
1784
reed@google.com4a3b7142012-05-16 17:16:46 +00001785SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001786 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001787
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001789 // Close the curve if requested and if there is some curve to close
1790 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001791 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 return kLine_Verb;
1793 }
1794 fNeedClose = false;
1795 return kClose_Verb;
1796 }
1797 return kDone_Verb;
1798 }
1799
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001800 // fVerbs is one beyond the current verb, decrement first
1801 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001802 const SkPoint* SK_RESTRICT srcPts = fPts;
1803 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804
1805 switch (verb) {
1806 case kMove_Verb:
1807 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 verb = this->autoClose(pts);
1810 if (verb == kClose_Verb) {
1811 fNeedClose = false;
1812 }
1813 return (Verb)verb;
1814 }
1815 if (fVerbs == fVerbStop) { // might be a trailing moveto
1816 return kDone_Verb;
1817 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001818 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001819 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001821 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 fNeedClose = fForceClose;
1824 break;
1825 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001826 pts[0] = this->cons_moveTo();
1827 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 fLastPt = srcPts[0];
1829 fCloseLine = false;
1830 srcPts += 1;
1831 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001832 case kConic_Verb:
1833 fConicWeights += 1;
1834 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001836 pts[0] = this->cons_moveTo();
1837 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001838 fLastPt = srcPts[1];
1839 srcPts += 2;
1840 break;
1841 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001842 pts[0] = this->cons_moveTo();
1843 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 fLastPt = srcPts[2];
1845 srcPts += 3;
1846 break;
1847 case kClose_Verb:
1848 verb = this->autoClose(pts);
1849 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 } else {
1852 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001853 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 break;
1857 }
1858 fPts = srcPts;
1859 return (Verb)verb;
1860}
1861
1862///////////////////////////////////////////////////////////////////////////////
1863
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001864SkPath::RawIter::RawIter() {
1865#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001866 fPts = nullptr;
1867 fConicWeights = nullptr;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001868#endif
1869 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001870 fVerbs = nullptr;
1871 fVerbStop = nullptr;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001872}
1873
1874SkPath::RawIter::RawIter(const SkPath& path) {
1875 this->setPath(path);
1876}
1877
1878void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001879 fPts = path.fPathRef->points();
1880 fVerbs = path.fPathRef->verbs();
1881 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001882 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001883}
1884
1885SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001886 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001887 if (fVerbs == fVerbStop) {
1888 return kDone_Verb;
1889 }
1890
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 // fVerbs points one beyond next verb so decrement first.
1892 unsigned verb = *(--fVerbs);
1893 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001894
1895 switch (verb) {
1896 case kMove_Verb:
reed6e434652015-05-27 19:53:25 -07001897 pts[0] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001898 srcPts += 1;
1899 break;
1900 case kLine_Verb:
reedb5615812015-05-13 07:55:48 -07001901 pts[0] = srcPts[-1];
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001902 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001903 srcPts += 1;
1904 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001905 case kConic_Verb:
1906 fConicWeights += 1;
1907 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001908 case kQuad_Verb:
reedb5615812015-05-13 07:55:48 -07001909 pts[0] = srcPts[-1];
1910 pts[1] = srcPts[0];
1911 pts[2] = srcPts[1];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001912 srcPts += 2;
1913 break;
1914 case kCubic_Verb:
reedb5615812015-05-13 07:55:48 -07001915 pts[0] = srcPts[-1];
1916 pts[1] = srcPts[0];
1917 pts[2] = srcPts[1];
1918 pts[3] = srcPts[2];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001919 srcPts += 3;
1920 break;
1921 case kClose_Verb:
reed6e434652015-05-27 19:53:25 -07001922 break;
1923 case kDone_Verb:
1924 SkASSERT(fVerbs == fVerbStop);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001925 break;
1926 }
1927 fPts = srcPts;
1928 return (Verb)verb;
1929}
1930
1931///////////////////////////////////////////////////////////////////////////////
1932
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001934 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935*/
1936
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001937size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 SkDEBUGCODE(this->validate();)
1939
halcanary96fcdcc2015-08-27 07:41:13 -07001940 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001941 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001942 return SkAlign4(byteCount);
1943 }
1944
1945 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001946
robertphillips@google.com466310d2013-12-03 16:43:54 +00001947 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001948 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001949 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001950 (fIsVolatile << kIsVolatile_SerializationShift) |
1951 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001952
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001953 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001954
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001956
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001957 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001958 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959}
1960
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001961size_t SkPath::readFromMemory(const void* storage, size_t length) {
1962 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001963
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001964 int32_t packed;
1965 if (!buffer.readS32(&packed)) {
1966 return 0;
1967 }
1968
reed8f086022015-06-11 14:22:19 -07001969 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001970
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001971 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1972 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001973 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001974 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001975 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001976
reed8f086022015-06-11 14:22:19 -07001977 // compatibility check
1978 if (version < kPathPrivFirstDirection_Version) {
1979 switch (dir) { // old values
1980 case 0:
1981 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1982 break;
1983 case 1:
1984 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1985 break;
1986 case 2:
1987 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1988 break;
1989 default:
1990 SkASSERT(false);
1991 }
1992 } else {
1993 fFirstDirection = dir;
1994 }
1995
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001996 size_t sizeRead = 0;
1997 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001998 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001999 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002000 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002001 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07002002 } else if (pathRef) {
halcanary96fcdcc2015-08-27 07:41:13 -07002003 // If the buffer is not valid, pathRef should be nullptr
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002004 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002005 }
2006 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007}
2008
2009///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010
reede05fed02014-12-15 07:59:53 -08002011#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002012#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002013
reed@google.com51bbe752013-01-17 22:07:50 +00002014static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002015 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002016 str->append(label);
2017 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002018
reed@google.com51bbe752013-01-17 22:07:50 +00002019 const SkScalar* values = &pts[0].fX;
2020 count *= 2;
2021
2022 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002023 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002024 if (i < count - 1) {
2025 str->append(", ");
2026 }
2027 }
reed@google.com277c3f82013-05-31 15:17:50 +00002028 if (conicWeight >= 0) {
2029 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002030 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002031 }
caryclark08fa28c2014-10-23 13:08:56 -07002032 str->append(");");
reede05fed02014-12-15 07:59:53 -08002033 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002034 str->append(" // ");
2035 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002036 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002037 if (i < count - 1) {
2038 str->append(", ");
2039 }
2040 }
2041 if (conicWeight >= 0) {
2042 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002043 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002044 }
2045 }
2046 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002047}
2048
caryclarke9562592014-09-15 09:26:09 -07002049void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002050 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 Iter iter(*this, forceClose);
2052 SkPoint pts[4];
2053 Verb verb;
2054
caryclark66a5d8b2014-06-24 08:30:15 -07002055 if (!wStream) {
2056 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2057 }
reed@google.com51bbe752013-01-17 22:07:50 +00002058 SkString builder;
2059
reed@google.com4a3b7142012-05-16 17:16:46 +00002060 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 switch (verb) {
2062 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002063 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 break;
2065 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002066 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 break;
2068 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002069 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002071 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002072 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002073 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002075 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076 break;
2077 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002078 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079 break;
2080 default:
2081 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2082 verb = kDone_Verb; // stop the loop
2083 break;
2084 }
caryclark1049f122015-04-20 08:31:59 -07002085 if (!wStream && builder.size()) {
2086 SkDebugf("%s", builder.c_str());
2087 builder.reset();
2088 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002089 }
caryclark66a5d8b2014-06-24 08:30:15 -07002090 if (wStream) {
2091 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002092 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093}
2094
reed@android.come522ca52009-11-23 20:10:41 +00002095void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002096 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002097}
2098
2099void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002100 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002101}
2102
2103#ifdef SK_DEBUG
2104void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002105 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002106
djsollen@google.com077348c2012-10-22 20:23:32 +00002107#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002108 if (!fBoundsIsDirty) {
2109 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002110
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002111 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002112 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002113
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002114 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002115 // if we're empty, fBounds may be empty but translated, so we can't
2116 // necessarily compare to bounds directly
2117 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2118 // be [2, 2, 2, 2]
2119 SkASSERT(bounds.isEmpty());
2120 SkASSERT(fBounds.isEmpty());
2121 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002122 if (bounds.isEmpty()) {
2123 SkASSERT(fBounds.isEmpty());
2124 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002125 if (!fBounds.isEmpty()) {
2126 SkASSERT(fBounds.contains(bounds));
2127 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002128 }
reed@android.come522ca52009-11-23 20:10:41 +00002129 }
2130 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002131#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002132}
djsollen@google.com077348c2012-10-22 20:23:32 +00002133#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002134
reed@google.com04863fa2011-05-15 04:08:24 +00002135///////////////////////////////////////////////////////////////////////////////
2136
reed@google.com0b7b9822011-05-16 12:29:27 +00002137static int sign(SkScalar x) { return x < 0; }
2138#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002139
robertphillipsc506e302014-09-16 09:43:31 -07002140enum DirChange {
2141 kLeft_DirChange,
2142 kRight_DirChange,
2143 kStraight_DirChange,
2144 kBackwards_DirChange,
2145
2146 kInvalid_DirChange
2147};
2148
2149
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002150static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002151 // The error epsilon was empirically derived; worse case round rects
2152 // with a mid point outset by 2x float epsilon in tests had an error
2153 // of 12.
2154 const int epsilon = 16;
2155 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2156 return false;
2157 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002158 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002159 int aBits = SkFloatAs2sCompliment(compA);
2160 int bBits = SkFloatAs2sCompliment(compB);
2161 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002162}
2163
caryclarkb4216502015-03-02 13:02:34 -08002164static bool approximately_zero_when_compared_to(double x, double y) {
2165 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002166}
2167
caryclarkb4216502015-03-02 13:02:34 -08002168
reed@google.com04863fa2011-05-15 04:08:24 +00002169// only valid for a single contour
2170struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002171 Convexicator()
2172 : fPtCount(0)
2173 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002174 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002175 , fIsFinite(true)
2176 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002177 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002178 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002179 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002180 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002181 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002182 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002183
2184 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002185 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002186 }
2187
2188 SkPath::Convexity getConvexity() const { return fConvexity; }
2189
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002190 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002191 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002192
reed@google.com04863fa2011-05-15 04:08:24 +00002193 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002194 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002195 return;
2196 }
2197
2198 if (0 == fPtCount) {
2199 fCurrPt = pt;
2200 ++fPtCount;
2201 } else {
2202 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002203 SkScalar lengthSqd = vec.lengthSqd();
2204 if (!SkScalarIsFinite(lengthSqd)) {
2205 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002206 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002207 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002208 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002209 fCurrPt = pt;
2210 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002211 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002212 } else {
2213 SkASSERT(fPtCount > 2);
2214 this->addVec(vec);
2215 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002216
reed@google.com85b6e392011-05-15 20:25:17 +00002217 int sx = sign(vec.fX);
2218 int sy = sign(vec.fY);
2219 fDx += (sx != fSx);
2220 fDy += (sy != fSy);
2221 fSx = sx;
2222 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002223
reed@google.com85b6e392011-05-15 20:25:17 +00002224 if (fDx > 3 || fDy > 3) {
2225 fConvexity = SkPath::kConcave_Convexity;
2226 }
reed@google.com04863fa2011-05-15 04:08:24 +00002227 }
2228 }
2229 }
2230
2231 void close() {
2232 if (fPtCount > 2) {
2233 this->addVec(fFirstVec);
2234 }
2235 }
2236
caryclarkb4216502015-03-02 13:02:34 -08002237 DirChange directionChange(const SkVector& curVec) {
2238 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2239
2240 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2241 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2242 largest = SkTMax(largest, -smallest);
2243
2244 if (!almost_equal(largest, largest + cross)) {
2245 int sign = SkScalarSignAsInt(cross);
2246 if (sign) {
2247 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2248 }
2249 }
2250
2251 if (cross) {
2252 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2253 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2254 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2255 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2256 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2257 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2258 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2259 if (sign) {
2260 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2261 }
2262 }
2263 }
2264
2265 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2266 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2267 fLastVec.dot(curVec) < 0.0f) {
2268 return kBackwards_DirChange;
2269 }
2270
2271 return kStraight_DirChange;
2272 }
2273
2274
caryclarkd3d1a982014-12-08 04:57:38 -08002275 bool isFinite() const {
2276 return fIsFinite;
2277 }
2278
caryclark5ccef572015-03-02 10:07:56 -08002279 void setCurve(bool isCurve) {
2280 fIsCurve = isCurve;
2281 }
2282
reed@google.com04863fa2011-05-15 04:08:24 +00002283private:
2284 void addVec(const SkVector& vec) {
2285 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002286 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002287 switch (dir) {
2288 case kLeft_DirChange: // fall through
2289 case kRight_DirChange:
2290 if (kInvalid_DirChange == fExpectedDir) {
2291 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002292 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2293 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002294 } else if (dir != fExpectedDir) {
2295 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002296 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002297 }
2298 fLastVec = vec;
2299 break;
2300 case kStraight_DirChange:
2301 break;
2302 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002303 if (fIsCurve) {
2304 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002305 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002306 }
robertphillipsc506e302014-09-16 09:43:31 -07002307 fLastVec = vec;
2308 break;
2309 case kInvalid_DirChange:
2310 SkFAIL("Use of invalid direction change flag");
2311 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002312 }
2313 }
2314
caryclarkb4216502015-03-02 13:02:34 -08002315 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002316 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002317 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002318 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2319 // value with the current vec is deemed to be of a significant value.
2320 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002321 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002322 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002323 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002324 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002325 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002326 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002327 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002328};
2329
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002330SkPath::Convexity SkPath::internalGetConvexity() const {
2331 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002332 SkPoint pts[4];
2333 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002334 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002335
2336 int contourCount = 0;
2337 int count;
2338 Convexicator state;
2339
caryclarkd3d1a982014-12-08 04:57:38 -08002340 if (!isFinite()) {
2341 return kUnknown_Convexity;
2342 }
caryclarke8c56662015-07-14 11:19:26 -07002343 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002344 switch (verb) {
2345 case kMove_Verb:
2346 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002347 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002348 return kConcave_Convexity;
2349 }
2350 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002351 // fall through
2352 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002353 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002354 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002355 break;
caryclark5ccef572015-03-02 10:07:56 -08002356 case kQuad_Verb:
2357 // fall through
2358 case kConic_Verb:
2359 // fall through
2360 case kCubic_Verb:
2361 count = 2 + (kCubic_Verb == verb);
2362 // As an additional enhancement, this could set curve true only
2363 // if the curve is nonlinear
2364 state.setCurve(true);
2365 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002366 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002367 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002368 state.close();
2369 count = 0;
2370 break;
2371 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002372 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002373 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002374 return kConcave_Convexity;
2375 }
2376
2377 for (int i = 1; i <= count; i++) {
2378 state.addPt(pts[i]);
2379 }
2380 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002381 if (!state.isFinite()) {
2382 return kUnknown_Convexity;
2383 }
reed@google.com04863fa2011-05-15 04:08:24 +00002384 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002385 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002386 return kConcave_Convexity;
2387 }
2388 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002389 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002390 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2391 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002392 }
2393 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002394}
reed@google.com69a99432012-01-10 18:00:10 +00002395
2396///////////////////////////////////////////////////////////////////////////////
2397
2398class ContourIter {
2399public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002400 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002401
2402 bool done() const { return fDone; }
2403 // if !done() then these may be called
2404 int count() const { return fCurrPtCount; }
2405 const SkPoint* pts() const { return fCurrPt; }
2406 void next();
2407
2408private:
2409 int fCurrPtCount;
2410 const SkPoint* fCurrPt;
2411 const uint8_t* fCurrVerb;
2412 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002413 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002414 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002415 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002416};
2417
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002418ContourIter::ContourIter(const SkPathRef& pathRef) {
2419 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002420 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002421 fCurrPt = pathRef.points();
2422 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002423 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002424 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002425 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002426 this->next();
2427}
2428
2429void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002430 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002431 fDone = true;
2432 }
2433 if (fDone) {
2434 return;
2435 }
2436
2437 // skip pts of prev contour
2438 fCurrPt += fCurrPtCount;
2439
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002440 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002441 int ptCount = 1; // moveTo
2442 const uint8_t* verbs = fCurrVerb;
2443
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002444 for (--verbs; verbs > fStopVerbs; --verbs) {
2445 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002446 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002447 goto CONTOUR_END;
2448 case SkPath::kLine_Verb:
2449 ptCount += 1;
2450 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002451 case SkPath::kConic_Verb:
2452 fCurrConicWeight += 1;
2453 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002454 case SkPath::kQuad_Verb:
2455 ptCount += 2;
2456 break;
2457 case SkPath::kCubic_Verb:
2458 ptCount += 3;
2459 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002460 case SkPath::kClose_Verb:
2461 break;
2462 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002463 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002464 break;
2465 }
2466 }
2467CONTOUR_END:
2468 fCurrPtCount = ptCount;
2469 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002470 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002471}
2472
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002473// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002474static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002475 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2476 // We may get 0 when the above subtracts underflow. We expect this to be
2477 // very rare and lazily promote to double.
2478 if (0 == cross) {
2479 double p0x = SkScalarToDouble(p0.fX);
2480 double p0y = SkScalarToDouble(p0.fY);
2481
2482 double p1x = SkScalarToDouble(p1.fX);
2483 double p1y = SkScalarToDouble(p1.fY);
2484
2485 double p2x = SkScalarToDouble(p2.fX);
2486 double p2y = SkScalarToDouble(p2.fY);
2487
2488 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2489 (p1y - p0y) * (p2x - p0x));
2490
2491 }
2492 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002493}
2494
reed@google.comc1ea60a2012-01-31 15:15:36 +00002495// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002496static int find_max_y(const SkPoint pts[], int count) {
2497 SkASSERT(count > 0);
2498 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002499 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002500 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002501 SkScalar y = pts[i].fY;
2502 if (y > max) {
2503 max = y;
2504 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002505 }
2506 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002507 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002508}
2509
reed@google.comcabaf1d2012-01-11 21:03:05 +00002510static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2511 int i = index;
2512 for (;;) {
2513 i = (i + inc) % n;
2514 if (i == index) { // we wrapped around, so abort
2515 break;
2516 }
2517 if (pts[index] != pts[i]) { // found a different point, success!
2518 break;
2519 }
2520 }
2521 return i;
2522}
2523
reed@google.comc1ea60a2012-01-31 15:15:36 +00002524/**
2525 * Starting at index, and moving forward (incrementing), find the xmin and
2526 * xmax of the contiguous points that have the same Y.
2527 */
2528static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2529 int* maxIndexPtr) {
2530 const SkScalar y = pts[index].fY;
2531 SkScalar min = pts[index].fX;
2532 SkScalar max = min;
2533 int minIndex = index;
2534 int maxIndex = index;
2535 for (int i = index + 1; i < n; ++i) {
2536 if (pts[i].fY != y) {
2537 break;
2538 }
2539 SkScalar x = pts[i].fX;
2540 if (x < min) {
2541 min = x;
2542 minIndex = i;
2543 } else if (x > max) {
2544 max = x;
2545 maxIndex = i;
2546 }
2547 }
2548 *maxIndexPtr = maxIndex;
2549 return minIndex;
2550}
2551
reed026beb52015-06-10 14:23:15 -07002552static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2553 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002554}
2555
reed@google.comac8543f2012-01-30 20:51:25 +00002556/*
2557 * We loop through all contours, and keep the computed cross-product of the
2558 * contour that contained the global y-max. If we just look at the first
2559 * contour, we may find one that is wound the opposite way (correctly) since
2560 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2561 * that is outer most (or at least has the global y-max) before we can consider
2562 * its cross product.
2563 */
reed026beb52015-06-10 14:23:15 -07002564bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002565 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2566 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002567 return true;
2568 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002569
2570 // don't want to pay the cost for computing this if it
2571 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002572 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2573 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002574 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002575 return false;
2576 }
reed@google.com69a99432012-01-10 18:00:10 +00002577
reed026beb52015-06-10 14:23:15 -07002578 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002579
reed@google.comac8543f2012-01-30 20:51:25 +00002580 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002581 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002582 SkScalar ymaxCross = 0;
2583
reed@google.com69a99432012-01-10 18:00:10 +00002584 for (; !iter.done(); iter.next()) {
2585 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002586 if (n < 3) {
2587 continue;
2588 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002589
reed@google.comcabaf1d2012-01-11 21:03:05 +00002590 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002591 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002592 int index = find_max_y(pts, n);
2593 if (pts[index].fY < ymax) {
2594 continue;
2595 }
2596
2597 // If there is more than 1 distinct point at the y-max, we take the
2598 // x-min and x-max of them and just subtract to compute the dir.
2599 if (pts[(index + 1) % n].fY == pts[index].fY) {
2600 int maxIndex;
2601 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2602 if (minIndex == maxIndex) {
2603 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002604 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002605 SkASSERT(pts[minIndex].fY == pts[index].fY);
2606 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2607 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2608 // we just subtract the indices, and let that auto-convert to
2609 // SkScalar, since we just want - or + to signal the direction.
2610 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002611 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002612 TRY_CROSSPROD:
2613 // Find a next and prev index to use for the cross-product test,
2614 // but we try to find pts that form non-zero vectors from pts[index]
2615 //
2616 // Its possible that we can't find two non-degenerate vectors, so
2617 // we have to guard our search (e.g. all the pts could be in the
2618 // same place).
2619
2620 // we pass n - 1 instead of -1 so we don't foul up % operator by
2621 // passing it a negative LH argument.
2622 int prev = find_diff_pt(pts, index, n, n - 1);
2623 if (prev == index) {
2624 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002625 continue;
2626 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002627 int next = find_diff_pt(pts, index, n, 1);
2628 SkASSERT(next != index);
2629 cross = cross_prod(pts[prev], pts[index], pts[next]);
2630 // if we get a zero and the points are horizontal, then we look at the spread in
2631 // x-direction. We really should continue to walk away from the degeneracy until
2632 // there is a divergence.
2633 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2634 // construct the subtract so we get the correct Direction below
2635 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002636 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002637 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002638
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002639 if (cross) {
2640 // record our best guess so far
2641 ymax = pts[index].fY;
2642 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002643 }
2644 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002645 if (ymaxCross) {
2646 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002647 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002648 return true;
2649 } else {
2650 return false;
2651 }
reed@google.comac8543f2012-01-30 20:51:25 +00002652}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002653
2654///////////////////////////////////////////////////////////////////////////////
2655
2656static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2657 SkScalar D, SkScalar t) {
2658 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2659}
2660
2661static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2662 SkScalar t) {
2663 SkScalar A = c3 + 3*(c1 - c2) - c0;
2664 SkScalar B = 3*(c2 - c1 - c1 + c0);
2665 SkScalar C = 3*(c1 - c0);
2666 SkScalar D = c0;
2667 return eval_cubic_coeff(A, B, C, D, t);
2668}
2669
2670/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2671 t value such that cubic(t) = target
2672 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002673static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002674 SkScalar target, SkScalar* t) {
2675 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2676 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002677
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002678 SkScalar D = c0 - target;
2679 SkScalar A = c3 + 3*(c1 - c2) - c0;
2680 SkScalar B = 3*(c2 - c1 - c1 + c0);
2681 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002682
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002683 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2684 SkScalar minT = 0;
2685 SkScalar maxT = SK_Scalar1;
2686 SkScalar mid;
2687 int i;
2688 for (i = 0; i < 16; i++) {
2689 mid = SkScalarAve(minT, maxT);
2690 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2691 if (delta < 0) {
2692 minT = mid;
2693 delta = -delta;
2694 } else {
2695 maxT = mid;
2696 }
2697 if (delta < TOLERANCE) {
2698 break;
2699 }
2700 }
2701 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002702}
2703
2704template <size_t N> static void find_minmax(const SkPoint pts[],
2705 SkScalar* minPtr, SkScalar* maxPtr) {
2706 SkScalar min, max;
2707 min = max = pts[0].fX;
2708 for (size_t i = 1; i < N; ++i) {
2709 min = SkMinScalar(min, pts[i].fX);
2710 max = SkMaxScalar(max, pts[i].fX);
2711 }
2712 *minPtr = min;
2713 *maxPtr = max;
2714}
2715
2716static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2717 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002718
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002719 int dir = 1;
2720 if (pts[0].fY > pts[3].fY) {
2721 storage[0] = pts[3];
2722 storage[1] = pts[2];
2723 storage[2] = pts[1];
2724 storage[3] = pts[0];
2725 pts = storage;
2726 dir = -1;
2727 }
2728 if (y < pts[0].fY || y >= pts[3].fY) {
2729 return 0;
2730 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002731
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002732 // quickreject or quickaccept
2733 SkScalar min, max;
2734 find_minmax<4>(pts, &min, &max);
2735 if (x < min) {
2736 return 0;
2737 }
2738 if (x > max) {
2739 return dir;
2740 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002741
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002742 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002743 SkScalar t;
2744 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2745 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002746 return xt < x ? dir : 0;
2747}
2748
2749static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2750 SkPoint dst[10];
2751 int n = SkChopCubicAtYExtrema(pts, dst);
2752 int w = 0;
2753 for (int i = 0; i <= n; ++i) {
2754 w += winding_mono_cubic(&dst[i * 3], x, y);
2755 }
2756 return w;
2757}
2758
2759static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2760 SkScalar y0 = pts[0].fY;
2761 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 int dir = 1;
2764 if (y0 > y2) {
2765 SkTSwap(y0, y2);
2766 dir = -1;
2767 }
2768 if (y < y0 || y >= y2) {
2769 return 0;
2770 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002771
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002772 // bounds check on X (not required. is it faster?)
2773#if 0
2774 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2775 return 0;
2776 }
2777#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002778
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002779 SkScalar roots[2];
2780 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2781 2 * (pts[1].fY - pts[0].fY),
2782 pts[0].fY - y,
2783 roots);
2784 SkASSERT(n <= 1);
2785 SkScalar xt;
2786 if (0 == n) {
2787 SkScalar mid = SkScalarAve(y0, y2);
2788 // Need [0] and [2] if dir == 1
2789 // and [2] and [0] if dir == -1
2790 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2791 } else {
2792 SkScalar t = roots[0];
2793 SkScalar C = pts[0].fX;
2794 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2795 SkScalar B = 2 * (pts[1].fX - C);
2796 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2797 }
2798 return xt < x ? dir : 0;
2799}
2800
2801static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2802 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2803 if (y0 == y1) {
2804 return true;
2805 }
2806 if (y0 < y1) {
2807 return y1 <= y2;
2808 } else {
2809 return y1 >= y2;
2810 }
2811}
2812
2813static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2814 SkPoint dst[5];
2815 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2818 n = SkChopQuadAtYExtrema(pts, dst);
2819 pts = dst;
2820 }
2821 int w = winding_mono_quad(pts, x, y);
2822 if (n > 0) {
2823 w += winding_mono_quad(&pts[2], x, y);
2824 }
2825 return w;
2826}
2827
2828static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2829 SkScalar x0 = pts[0].fX;
2830 SkScalar y0 = pts[0].fY;
2831 SkScalar x1 = pts[1].fX;
2832 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002833
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002834 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 int dir = 1;
2837 if (y0 > y1) {
2838 SkTSwap(y0, y1);
2839 dir = -1;
2840 }
2841 if (y < y0 || y >= y1) {
2842 return 0;
2843 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002844
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2846 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002847
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 if (SkScalarSignAsInt(cross) == dir) {
2849 dir = 0;
2850 }
2851 return dir;
2852}
2853
reed@google.com4db592c2013-10-30 17:39:43 +00002854static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2855 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2856}
2857
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002858bool SkPath::contains(SkScalar x, SkScalar y) const {
2859 bool isInverse = this->isInverseFillType();
2860 if (this->isEmpty()) {
2861 return isInverse;
2862 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002863
reed@google.com4db592c2013-10-30 17:39:43 +00002864 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002865 return isInverse;
2866 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002867
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002868 SkPath::Iter iter(*this, true);
2869 bool done = false;
2870 int w = 0;
2871 do {
2872 SkPoint pts[4];
2873 switch (iter.next(pts, false)) {
2874 case SkPath::kMove_Verb:
2875 case SkPath::kClose_Verb:
2876 break;
2877 case SkPath::kLine_Verb:
2878 w += winding_line(pts, x, y);
2879 break;
2880 case SkPath::kQuad_Verb:
2881 w += winding_quad(pts, x, y);
2882 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002883 case SkPath::kConic_Verb:
2884 SkASSERT(0);
2885 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 case SkPath::kCubic_Verb:
2887 w += winding_cubic(pts, x, y);
2888 break;
2889 case SkPath::kDone_Verb:
2890 done = true;
2891 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002892 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002893 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002894
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 switch (this->getFillType()) {
2896 case SkPath::kEvenOdd_FillType:
2897 case SkPath::kInverseEvenOdd_FillType:
2898 w &= 1;
2899 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002900 default:
2901 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002902 }
2903 return SkToBool(w);
2904}