blob: 97e34386dc1ce3aeeb8b09919cbeecc3096b35fa [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) {
183 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
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000843static void assert_known_direction(int dir) {
844 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
845}
846
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847void SkPath::addRect(const SkRect& rect, Direction dir) {
848 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
849}
850
851void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
852 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000853 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700854 fFirstDirection = this->hasOnlyMoveTos() ?
855 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000856 SkAutoDisableDirectionCheck addc(this);
857
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
859
860 this->incReserve(5);
861
862 this->moveTo(left, top);
863 if (dir == kCCW_Direction) {
864 this->lineTo(left, bottom);
865 this->lineTo(right, bottom);
866 this->lineTo(right, top);
867 } else {
868 this->lineTo(right, top);
869 this->lineTo(right, bottom);
870 this->lineTo(left, bottom);
871 }
872 this->close();
873}
874
reed@google.com744faba2012-05-29 19:54:52 +0000875void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
876 SkDEBUGCODE(this->validate();)
877 if (count <= 0) {
878 return;
879 }
880
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000881 fLastMoveToIndex = fPathRef->countPoints();
882
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000883 // +close makes room for the extra kClose_Verb
884 SkPathRef::Editor ed(&fPathRef, count+close, count);
885
886 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000887 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000888 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
889 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000890 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000891
reed@google.com744faba2012-05-29 19:54:52 +0000892 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000893 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800894 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000895 }
896
reed@google.com744faba2012-05-29 19:54:52 +0000897 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000898 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000899}
900
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000901#include "SkGeometry.h"
902
reedf90ea012015-01-29 12:03:58 -0800903static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
904 SkPoint* pt) {
905 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000906 // Chrome uses this path to move into and out of ovals. If not
907 // treated as a special case the moves can distort the oval's
908 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -0800909 pt->set(oval.fRight, oval.centerY());
910 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000911 } else if (0 == oval.width() && 0 == oval.height()) {
912 // Chrome will sometimes create 0 radius round rects. Having degenerate
913 // quad segments in the path prevents the path from being recognized as
914 // a rect.
915 // TODO: optimizing the case where only one of width or height is zero
916 // should also be considered. This case, however, doesn't seem to be
917 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -0800918 pt->set(oval.fRight, oval.fTop);
919 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000920 }
reedf90ea012015-01-29 12:03:58 -0800921 return false;
922}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000923
reedd5d27d92015-02-09 13:54:43 -0800924// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
925//
926static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
927 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
928 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
929 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000930
931 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -0800932 loss in radians-conversion and/or sin/cos, we may end up with coincident
933 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
934 of drawing a nearly complete circle (good).
935 e.g. canvas.drawArc(0, 359.99, ...)
936 -vs- canvas.drawArc(0, 359.9, ...)
937 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000938 */
reedd5d27d92015-02-09 13:54:43 -0800939 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000940 SkScalar sw = SkScalarAbs(sweepAngle);
941 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
942 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
943 // make a guess at a tiny angle (in radians) to tweak by
944 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
945 // not sure how much will be enough, so we use a loop
946 do {
947 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -0800948 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
949 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000950 }
951 }
reedd5d27d92015-02-09 13:54:43 -0800952 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
953}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000954
reed9e779d42015-02-17 11:43:14 -0800955/**
956 * If this returns 0, then the caller should just line-to the singlePt, else it should
957 * ignore singlePt and append the specified number of conics.
958 */
reedd5d27d92015-02-09 13:54:43 -0800959static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -0800960 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
961 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -0800962 SkMatrix matrix;
963
964 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
965 matrix.postTranslate(oval.centerX(), oval.centerY());
966
reed9e779d42015-02-17 11:43:14 -0800967 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
968 if (0 == count) {
969 matrix.mapXY(start.x(), start.y(), singlePt);
970 }
971 return count;
reedd5d27d92015-02-09 13:54:43 -0800972}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000973
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000974void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000976 SkRRect rrect;
977 rrect.setRectRadii(rect, (const SkVector*) radii);
978 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979}
980
reed@google.com4ed0fb72012-12-12 20:48:18 +0000981void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000982 assert_known_direction(dir);
983
984 if (rrect.isEmpty()) {
985 return;
986 }
987
reed@google.com4ed0fb72012-12-12 20:48:18 +0000988 const SkRect& bounds = rrect.getBounds();
989
990 if (rrect.isRect()) {
991 this->addRect(bounds, dir);
992 } else if (rrect.isOval()) {
993 this->addOval(bounds, dir);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000994 } else {
reed026beb52015-06-10 14:23:15 -0700995 fFirstDirection = this->hasOnlyMoveTos() ?
996 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000997
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000998 SkAutoPathBoundsUpdate apbu(this, bounds);
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +0000999 SkAutoDisableDirectionCheck addc(this);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001000
reed1b28a3a2014-12-17 14:42:09 -08001001 const SkScalar L = bounds.fLeft;
1002 const SkScalar T = bounds.fTop;
1003 const SkScalar R = bounds.fRight;
1004 const SkScalar B = bounds.fBottom;
1005 const SkScalar W = SK_ScalarRoot2Over2;
1006
1007 this->incReserve(13);
1008 if (kCW_Direction == dir) {
1009 this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1010
1011 this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1012 this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
1013
1014 this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
1015 this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
1016
1017 this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
1018 this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
1019
1020 this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
1021 this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
1022 } else {
1023 this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1024
1025 this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1026 this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
1027
1028 this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
1029 this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
1030
1031 this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
1032 this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
1033
1034 this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
1035 this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
1036 }
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001037 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001038 }
reed5bcbe912014-12-15 12:28:33 -08001039 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001040}
1041
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001042bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001043 int count = fPathRef->countVerbs();
1044 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1045 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001046 if (*verbs == kLine_Verb ||
1047 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001048 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001049 *verbs == kCubic_Verb) {
1050 return false;
1051 }
1052 ++verbs;
1053 }
1054 return true;
1055}
1056
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001057void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1058 Direction dir) {
1059 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001060
humper@google.com75e3ca12013-04-08 21:44:11 +00001061 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001062 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001063 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001064 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001065 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1066 return;
1067 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001068
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001069 SkRRect rrect;
1070 rrect.setRectXY(rect, rx, ry);
1071 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001072}
1073
reed@android.com8a1c16f2008-12-17 15:59:43 +00001074void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001075 assert_known_direction(dir);
1076
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001077 /* If addOval() is called after previous moveTo(),
1078 this path is still marked as an oval. This is used to
1079 fit into WebKit's calling sequences.
1080 We can't simply check isEmpty() in this case, as additional
1081 moveTo() would mark the path non empty.
1082 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001083 bool isOval = hasOnlyMoveTos();
1084 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001085 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001086 } else {
reed026beb52015-06-10 14:23:15 -07001087 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001088 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001089
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001090 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001091
reed@android.com8a1c16f2008-12-17 15:59:43 +00001092 SkAutoPathBoundsUpdate apbu(this, oval);
1093
reed220f9262014-12-17 08:21:04 -08001094 const SkScalar L = oval.fLeft;
1095 const SkScalar T = oval.fTop;
1096 const SkScalar R = oval.fRight;
1097 const SkScalar B = oval.fBottom;
1098 const SkScalar cx = oval.centerX();
1099 const SkScalar cy = oval.centerY();
1100 const SkScalar weight = SK_ScalarRoot2Over2;
1101
1102 this->incReserve(9); // move + 4 conics
1103 this->moveTo(R, cy);
1104 if (dir == kCCW_Direction) {
1105 this->conicTo(R, T, cx, T, weight);
1106 this->conicTo(L, T, L, cy, weight);
1107 this->conicTo(L, B, cx, B, weight);
1108 this->conicTo(R, B, R, cy, weight);
1109 } else {
1110 this->conicTo(R, B, cx, B, weight);
1111 this->conicTo(L, B, L, cy, weight);
1112 this->conicTo(L, T, cx, T, weight);
1113 this->conicTo(R, T, R, cy, weight);
1114 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001115 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116
robertphillips@google.com466310d2013-12-03 16:43:54 +00001117 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001118
robertphillips@google.com466310d2013-12-03 16:43:54 +00001119 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001120}
1121
reed@android.com8a1c16f2008-12-17 15:59:43 +00001122void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1123 if (r > 0) {
1124 SkRect rect;
1125 rect.set(x - r, y - r, x + r, y + r);
1126 this->addOval(rect, dir);
1127 }
1128}
1129
reed@android.com8a1c16f2008-12-17 15:59:43 +00001130void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1131 bool forceMoveTo) {
1132 if (oval.width() < 0 || oval.height() < 0) {
1133 return;
1134 }
1135
reedf90ea012015-01-29 12:03:58 -08001136 if (fPathRef->countVerbs() == 0) {
1137 forceMoveTo = true;
1138 }
1139
1140 SkPoint lonePt;
1141 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1142 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1143 return;
1144 }
1145
reedd5d27d92015-02-09 13:54:43 -08001146 SkVector startV, stopV;
1147 SkRotationDirection dir;
1148 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1149
reed9e779d42015-02-17 11:43:14 -08001150 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001151 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001152 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001153 if (count) {
1154 this->incReserve(count * 2 + 1);
1155 const SkPoint& pt = conics[0].fPts[0];
1156 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1157 for (int i = 0; i < count; ++i) {
1158 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1159 }
reed9e779d42015-02-17 11:43:14 -08001160 } else {
1161 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001162 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001163}
1164
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001165void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001166 if (oval.isEmpty() || 0 == sweepAngle) {
1167 return;
1168 }
1169
1170 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1171
1172 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1173 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001174 } else {
1175 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001176 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001177}
1178
1179/*
1180 Need to handle the case when the angle is sharp, and our computed end-points
1181 for the arc go behind pt1 and/or p2...
1182*/
reedc7789042015-01-29 12:59:11 -08001183void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001184 if (radius == 0) {
1185 this->lineTo(x1, y1);
1186 return;
1187 }
1188
1189 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001190
reed@android.com8a1c16f2008-12-17 15:59:43 +00001191 // need to know our prev pt so we can construct tangent vectors
1192 {
1193 SkPoint start;
1194 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001195 // Handle degenerate cases by adding a line to the first point and
1196 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 before.setNormalize(x1 - start.fX, y1 - start.fY);
1198 after.setNormalize(x2 - x1, y2 - y1);
1199 }
reed@google.comabf15c12011-01-18 20:35:51 +00001200
reed@android.com8a1c16f2008-12-17 15:59:43 +00001201 SkScalar cosh = SkPoint::DotProduct(before, after);
1202 SkScalar sinh = SkPoint::CrossProduct(before, after);
1203
1204 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001205 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 return;
1207 }
reed@google.comabf15c12011-01-18 20:35:51 +00001208
reed@android.com8a1c16f2008-12-17 15:59:43 +00001209 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1210 if (dist < 0) {
1211 dist = -dist;
1212 }
1213
1214 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1215 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1216 SkRotationDirection arcDir;
1217
1218 // now turn before/after into normals
1219 if (sinh > 0) {
1220 before.rotateCCW();
1221 after.rotateCCW();
1222 arcDir = kCW_SkRotationDirection;
1223 } else {
1224 before.rotateCW();
1225 after.rotateCW();
1226 arcDir = kCCW_SkRotationDirection;
1227 }
1228
1229 SkMatrix matrix;
1230 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 matrix.setScale(radius, radius);
1233 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1234 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001235
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001237
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238 this->incReserve(count);
1239 // [xx,yy] == pts[0]
1240 this->lineTo(xx, yy);
1241 for (int i = 1; i < count; i += 2) {
1242 this->quadTo(pts[i], pts[i+1]);
1243 }
1244}
1245
1246///////////////////////////////////////////////////////////////////////////////
1247
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001248void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkMatrix matrix;
1250
1251 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001252 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001253}
1254
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001255void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001256 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001258 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 SkPoint pts[4];
1260 Verb verb;
1261
1262 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001263 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 while ((verb = iter.next(pts)) != kDone_Verb) {
1265 switch (verb) {
1266 case kMove_Verb:
1267 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001268 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1269 injectMoveToIfNeeded(); // In case last contour is closed
1270 this->lineTo(pts[0]);
1271 } else {
1272 this->moveTo(pts[0]);
1273 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001274 break;
1275 case kLine_Verb:
1276 proc(matrix, &pts[1], &pts[1], 1);
1277 this->lineTo(pts[1]);
1278 break;
1279 case kQuad_Verb:
1280 proc(matrix, &pts[1], &pts[1], 2);
1281 this->quadTo(pts[1], pts[2]);
1282 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001283 case kConic_Verb:
1284 proc(matrix, &pts[1], &pts[1], 2);
1285 this->conicTo(pts[1], pts[2], iter.conicWeight());
1286 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 case kCubic_Verb:
1288 proc(matrix, &pts[1], &pts[1], 3);
1289 this->cubicTo(pts[1], pts[2], pts[3]);
1290 break;
1291 case kClose_Verb:
1292 this->close();
1293 break;
1294 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001295 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001297 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001298 }
1299}
1300
1301///////////////////////////////////////////////////////////////////////////////
1302
reed@google.com277c3f82013-05-31 15:17:50 +00001303static int pts_in_verb(unsigned verb) {
1304 static const uint8_t gPtsInVerb[] = {
1305 1, // kMove
1306 1, // kLine
1307 2, // kQuad
1308 2, // kConic
1309 3, // kCubic
1310 0, // kClose
1311 0 // kDone
1312 };
1313
1314 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1315 return gPtsInVerb[verb];
1316}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001317
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318// ignore the last point of the 1st contour
1319void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001320 int i, vcount = path.fPathRef->countVerbs();
1321 // exit early if the path is empty, or just has a moveTo.
1322 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 return;
1324 }
1325
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001326 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001328 const uint8_t* verbs = path.fPathRef->verbs();
1329 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001330 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001332 SkASSERT(verbs[~0] == kMove_Verb);
1333 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001334 unsigned v = verbs[~i];
1335 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 if (n == 0) {
1337 break;
1338 }
1339 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001340 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 }
1342
1343 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001344 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 case kLine_Verb:
1346 this->lineTo(pts[-1].fX, pts[-1].fY);
1347 break;
1348 case kQuad_Verb:
1349 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1350 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001351 case kConic_Verb:
1352 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1353 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 case kCubic_Verb:
1355 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1356 pts[-3].fX, pts[-3].fY);
1357 break;
1358 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001359 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 break;
1361 }
reed@google.com277c3f82013-05-31 15:17:50 +00001362 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 }
1364}
1365
reed@google.com63d73742012-01-10 15:33:12 +00001366void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001367 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001368
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001369 const SkPoint* pts = src.fPathRef->pointsEnd();
1370 // we will iterator through src's verbs backwards
1371 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1372 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001373 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001374
1375 bool needMove = true;
1376 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001377 while (verbs < verbsEnd) {
1378 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001379 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001380
1381 if (needMove) {
1382 --pts;
1383 this->moveTo(pts->fX, pts->fY);
1384 needMove = false;
1385 }
1386 pts -= n;
1387 switch (v) {
1388 case kMove_Verb:
1389 if (needClose) {
1390 this->close();
1391 needClose = false;
1392 }
1393 needMove = true;
1394 pts += 1; // so we see the point in "if (needMove)" above
1395 break;
1396 case kLine_Verb:
1397 this->lineTo(pts[0]);
1398 break;
1399 case kQuad_Verb:
1400 this->quadTo(pts[1], pts[0]);
1401 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001402 case kConic_Verb:
1403 this->conicTo(pts[1], pts[0], *--conicWeights);
1404 break;
reed@google.com63d73742012-01-10 15:33:12 +00001405 case kCubic_Verb:
1406 this->cubicTo(pts[2], pts[1], pts[0]);
1407 break;
1408 case kClose_Verb:
1409 needClose = true;
1410 break;
1411 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001412 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001413 }
1414 }
1415}
1416
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417///////////////////////////////////////////////////////////////////////////////
1418
1419void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1420 SkMatrix matrix;
1421
1422 matrix.setTranslate(dx, dy);
1423 this->transform(matrix, dst);
1424}
1425
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1427 int level = 2) {
1428 if (--level >= 0) {
1429 SkPoint tmp[7];
1430
1431 SkChopCubicAtHalf(pts, tmp);
1432 subdivide_cubic_to(path, &tmp[0], level);
1433 subdivide_cubic_to(path, &tmp[3], level);
1434 } else {
1435 path->cubicTo(pts[1], pts[2], pts[3]);
1436 }
1437}
1438
1439void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1440 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001441 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 dst = (SkPath*)this;
1443 }
1444
tomhudson@google.com8d430182011-06-06 19:11:19 +00001445 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446 SkPath tmp;
1447 tmp.fFillType = fFillType;
1448
1449 SkPath::Iter iter(*this, false);
1450 SkPoint pts[4];
1451 SkPath::Verb verb;
1452
reed@google.com4a3b7142012-05-16 17:16:46 +00001453 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 switch (verb) {
1455 case kMove_Verb:
1456 tmp.moveTo(pts[0]);
1457 break;
1458 case kLine_Verb:
1459 tmp.lineTo(pts[1]);
1460 break;
1461 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001462 // promote the quad to a conic
1463 tmp.conicTo(pts[1], pts[2],
1464 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001466 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001467 tmp.conicTo(pts[1], pts[2],
1468 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001469 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 case kCubic_Verb:
1471 subdivide_cubic_to(&tmp, pts);
1472 break;
1473 case kClose_Verb:
1474 tmp.close();
1475 break;
1476 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001477 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001478 break;
1479 }
1480 }
1481
1482 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 SkPathRef::Editor ed(&dst->fPathRef);
1484 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001485 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001487 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1488
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001491 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001492 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001494
reed026beb52015-06-10 14:23:15 -07001495 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1496 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001497 } else {
1498 SkScalar det2x2 =
1499 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1500 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1501 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001502 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1503 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001504 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001505 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001506 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001507 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001508 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001509 }
1510 }
1511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512 SkDEBUGCODE(dst->validate();)
1513 }
1514}
1515
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516///////////////////////////////////////////////////////////////////////////////
1517///////////////////////////////////////////////////////////////////////////////
1518
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001519enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001520 kEmptyContour_SegmentState, // The current contour is empty. We may be
1521 // starting processing or we may have just
1522 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001523 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1524 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1525 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526};
1527
1528SkPath::Iter::Iter() {
1529#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001530 fPts = nullptr;
1531 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001533 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001534 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535#endif
1536 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001537 fVerbs = nullptr;
1538 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 fNeedClose = false;
1540}
1541
1542SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1543 this->setPath(path, forceClose);
1544}
1545
1546void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 fPts = path.fPathRef->points();
1548 fVerbs = path.fPathRef->verbs();
1549 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001550 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001551 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001552 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 fForceClose = SkToU8(forceClose);
1554 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001555 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556}
1557
1558bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001559 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 return false;
1561 }
1562 if (fForceClose) {
1563 return true;
1564 }
1565
1566 const uint8_t* verbs = fVerbs;
1567 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001568
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 if (kMove_Verb == *(verbs - 1)) {
1570 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 }
1572
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001573 while (verbs > stop) {
1574 // verbs points one beyond the current verb, decrement first.
1575 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 if (kMove_Verb == v) {
1577 break;
1578 }
1579 if (kClose_Verb == v) {
1580 return true;
1581 }
1582 }
1583 return false;
1584}
1585
1586SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001587 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001589 // A special case: if both points are NaN, SkPoint::operation== returns
1590 // false, but the iterator expects that they are treated as the same.
1591 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001592 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1593 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001594 return kClose_Verb;
1595 }
1596
reed@google.com9e25dbf2012-05-15 17:05:38 +00001597 pts[0] = fLastPt;
1598 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 fLastPt = fMoveTo;
1600 fCloseLine = true;
1601 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001602 } else {
1603 pts[0] = fMoveTo;
1604 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606}
1607
reed@google.com9e25dbf2012-05-15 17:05:38 +00001608const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001609 if (fSegmentState == kAfterMove_SegmentState) {
1610 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001611 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001612 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001614 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1615 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001616 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618}
1619
caryclarke8c56662015-07-14 11:19:26 -07001620void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001621 // We need to step over anything that will not move the current draw point
1622 // forward before the next move is seen
1623 const uint8_t* lastMoveVerb = 0;
1624 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001625 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001626 SkPoint lastPt = fLastPt;
1627 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001628 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001629 switch (verb) {
1630 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001631 // Keep a record of this most recent move
1632 lastMoveVerb = fVerbs;
1633 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001634 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001635 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001636 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001637 fPts++;
1638 break;
1639
1640 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001641 // A close when we are in a segment is always valid except when it
1642 // follows a move which follows a segment.
1643 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001644 return;
1645 }
1646 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001647 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001648 break;
1649
1650 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001651 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001652 if (lastMoveVerb) {
1653 fVerbs = lastMoveVerb;
1654 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001655 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001656 return;
1657 }
1658 return;
1659 }
1660 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001661 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001662 fPts++;
1663 break;
1664
reed@google.com277c3f82013-05-31 15:17:50 +00001665 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001666 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001667 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001668 if (lastMoveVerb) {
1669 fVerbs = lastMoveVerb;
1670 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001671 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001672 return;
1673 }
1674 return;
1675 }
1676 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001678 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001679 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001680 break;
1681
1682 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001683 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001684 if (lastMoveVerb) {
1685 fVerbs = lastMoveVerb;
1686 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001687 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001688 return;
1689 }
1690 return;
1691 }
1692 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001694 fPts += 3;
1695 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001696
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001697 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001698 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001699 }
1700 }
1701}
1702
reed@google.com4a3b7142012-05-16 17:16:46 +00001703SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001704 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001705
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001707 // Close the curve if requested and if there is some curve to close
1708 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001709 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 return kLine_Verb;
1711 }
1712 fNeedClose = false;
1713 return kClose_Verb;
1714 }
1715 return kDone_Verb;
1716 }
1717
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001718 // fVerbs is one beyond the current verb, decrement first
1719 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001720 const SkPoint* SK_RESTRICT srcPts = fPts;
1721 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722
1723 switch (verb) {
1724 case kMove_Verb:
1725 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001726 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 verb = this->autoClose(pts);
1728 if (verb == kClose_Verb) {
1729 fNeedClose = false;
1730 }
1731 return (Verb)verb;
1732 }
1733 if (fVerbs == fVerbStop) { // might be a trailing moveto
1734 return kDone_Verb;
1735 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001736 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001737 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001739 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001740 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741 fNeedClose = fForceClose;
1742 break;
1743 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001744 pts[0] = this->cons_moveTo();
1745 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 fLastPt = srcPts[0];
1747 fCloseLine = false;
1748 srcPts += 1;
1749 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001750 case kConic_Verb:
1751 fConicWeights += 1;
1752 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001754 pts[0] = this->cons_moveTo();
1755 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 fLastPt = srcPts[1];
1757 srcPts += 2;
1758 break;
1759 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001760 pts[0] = this->cons_moveTo();
1761 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 fLastPt = srcPts[2];
1763 srcPts += 3;
1764 break;
1765 case kClose_Verb:
1766 verb = this->autoClose(pts);
1767 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001768 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 } else {
1770 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001771 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 break;
1775 }
1776 fPts = srcPts;
1777 return (Verb)verb;
1778}
1779
1780///////////////////////////////////////////////////////////////////////////////
1781
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001782SkPath::RawIter::RawIter() {
1783#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001784 fPts = nullptr;
1785 fConicWeights = nullptr;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001786#endif
1787 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001788 fVerbs = nullptr;
1789 fVerbStop = nullptr;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001790}
1791
1792SkPath::RawIter::RawIter(const SkPath& path) {
1793 this->setPath(path);
1794}
1795
1796void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001797 fPts = path.fPathRef->points();
1798 fVerbs = path.fPathRef->verbs();
1799 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001800 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001801}
1802
1803SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon49f085d2014-09-05 13:34:00 -07001804 SkASSERT(pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001805 if (fVerbs == fVerbStop) {
1806 return kDone_Verb;
1807 }
1808
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001809 // fVerbs points one beyond next verb so decrement first.
1810 unsigned verb = *(--fVerbs);
1811 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001812
1813 switch (verb) {
1814 case kMove_Verb:
reed6e434652015-05-27 19:53:25 -07001815 pts[0] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001816 srcPts += 1;
1817 break;
1818 case kLine_Verb:
reedb5615812015-05-13 07:55:48 -07001819 pts[0] = srcPts[-1];
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001820 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001821 srcPts += 1;
1822 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001823 case kConic_Verb:
1824 fConicWeights += 1;
1825 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001826 case kQuad_Verb:
reedb5615812015-05-13 07:55:48 -07001827 pts[0] = srcPts[-1];
1828 pts[1] = srcPts[0];
1829 pts[2] = srcPts[1];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001830 srcPts += 2;
1831 break;
1832 case kCubic_Verb:
reedb5615812015-05-13 07:55:48 -07001833 pts[0] = srcPts[-1];
1834 pts[1] = srcPts[0];
1835 pts[2] = srcPts[1];
1836 pts[3] = srcPts[2];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001837 srcPts += 3;
1838 break;
1839 case kClose_Verb:
reed6e434652015-05-27 19:53:25 -07001840 break;
1841 case kDone_Verb:
1842 SkASSERT(fVerbs == fVerbStop);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001843 break;
1844 }
1845 fPts = srcPts;
1846 return (Verb)verb;
1847}
1848
1849///////////////////////////////////////////////////////////////////////////////
1850
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001852 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853*/
1854
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001855size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 SkDEBUGCODE(this->validate();)
1857
halcanary96fcdcc2015-08-27 07:41:13 -07001858 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001859 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001860 return SkAlign4(byteCount);
1861 }
1862
1863 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001864
robertphillips@google.com466310d2013-12-03 16:43:54 +00001865 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001866 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001867 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001868 (fIsVolatile << kIsVolatile_SerializationShift) |
1869 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001870
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001871 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001872
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001873 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001874
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001875 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001876 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877}
1878
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001879size_t SkPath::readFromMemory(const void* storage, size_t length) {
1880 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001881
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001882 int32_t packed;
1883 if (!buffer.readS32(&packed)) {
1884 return 0;
1885 }
1886
reed8f086022015-06-11 14:22:19 -07001887 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001888
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001889 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1890 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001891 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001892 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001893 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
reed@google.comabf15c12011-01-18 20:35:51 +00001894
reed8f086022015-06-11 14:22:19 -07001895 // compatibility check
1896 if (version < kPathPrivFirstDirection_Version) {
1897 switch (dir) { // old values
1898 case 0:
1899 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1900 break;
1901 case 1:
1902 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1903 break;
1904 case 2:
1905 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1906 break;
1907 default:
1908 SkASSERT(false);
1909 }
1910 } else {
1911 fFirstDirection = dir;
1912 }
1913
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001914 size_t sizeRead = 0;
1915 if (buffer.isValid()) {
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001916 fPathRef.reset(pathRef);
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001917 SkDEBUGCODE(this->validate();)
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001918 buffer.skipToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001919 sizeRead = buffer.pos();
bsalomon49f085d2014-09-05 13:34:00 -07001920 } else if (pathRef) {
halcanary96fcdcc2015-08-27 07:41:13 -07001921 // If the buffer is not valid, pathRef should be nullptr
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001922 sk_throw();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001923 }
1924 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925}
1926
1927///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001928
reede05fed02014-12-15 07:59:53 -08001929#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001930#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001931
reed@google.com51bbe752013-01-17 22:07:50 +00001932static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001933 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001934 str->append(label);
1935 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001936
reed@google.com51bbe752013-01-17 22:07:50 +00001937 const SkScalar* values = &pts[0].fX;
1938 count *= 2;
1939
1940 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001941 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001942 if (i < count - 1) {
1943 str->append(", ");
1944 }
1945 }
reed@google.com277c3f82013-05-31 15:17:50 +00001946 if (conicWeight >= 0) {
1947 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001948 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001949 }
caryclark08fa28c2014-10-23 13:08:56 -07001950 str->append(");");
reede05fed02014-12-15 07:59:53 -08001951 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001952 str->append(" // ");
1953 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001954 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001955 if (i < count - 1) {
1956 str->append(", ");
1957 }
1958 }
1959 if (conicWeight >= 0) {
1960 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001961 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001962 }
1963 }
1964 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001965}
1966
caryclarke9562592014-09-15 09:26:09 -07001967void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001968 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969 Iter iter(*this, forceClose);
1970 SkPoint pts[4];
1971 Verb verb;
1972
caryclark66a5d8b2014-06-24 08:30:15 -07001973 if (!wStream) {
1974 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1975 }
reed@google.com51bbe752013-01-17 22:07:50 +00001976 SkString builder;
1977
reed@google.com4a3b7142012-05-16 17:16:46 +00001978 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001979 switch (verb) {
1980 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08001981 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 break;
1983 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08001984 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985 break;
1986 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08001987 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001988 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001989 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08001990 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00001991 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08001993 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001994 break;
1995 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07001996 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001997 break;
1998 default:
1999 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2000 verb = kDone_Verb; // stop the loop
2001 break;
2002 }
caryclark1049f122015-04-20 08:31:59 -07002003 if (!wStream && builder.size()) {
2004 SkDebugf("%s", builder.c_str());
2005 builder.reset();
2006 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 }
caryclark66a5d8b2014-06-24 08:30:15 -07002008 if (wStream) {
2009 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002010 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011}
2012
reed@android.come522ca52009-11-23 20:10:41 +00002013void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002014 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002015}
2016
2017void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002018 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002019}
2020
2021#ifdef SK_DEBUG
2022void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002023 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002024
djsollen@google.com077348c2012-10-22 20:23:32 +00002025#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002026 if (!fBoundsIsDirty) {
2027 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002028
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002029 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002030 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002031
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002032 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002033 // if we're empty, fBounds may be empty but translated, so we can't
2034 // necessarily compare to bounds directly
2035 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2036 // be [2, 2, 2, 2]
2037 SkASSERT(bounds.isEmpty());
2038 SkASSERT(fBounds.isEmpty());
2039 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002040 if (bounds.isEmpty()) {
2041 SkASSERT(fBounds.isEmpty());
2042 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002043 if (!fBounds.isEmpty()) {
2044 SkASSERT(fBounds.contains(bounds));
2045 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002046 }
reed@android.come522ca52009-11-23 20:10:41 +00002047 }
2048 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002049#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002050}
djsollen@google.com077348c2012-10-22 20:23:32 +00002051#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002052
reed@google.com04863fa2011-05-15 04:08:24 +00002053///////////////////////////////////////////////////////////////////////////////
2054
reed@google.com0b7b9822011-05-16 12:29:27 +00002055static int sign(SkScalar x) { return x < 0; }
2056#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002057
robertphillipsc506e302014-09-16 09:43:31 -07002058enum DirChange {
2059 kLeft_DirChange,
2060 kRight_DirChange,
2061 kStraight_DirChange,
2062 kBackwards_DirChange,
2063
2064 kInvalid_DirChange
2065};
2066
2067
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002068static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002069 // The error epsilon was empirically derived; worse case round rects
2070 // with a mid point outset by 2x float epsilon in tests had an error
2071 // of 12.
2072 const int epsilon = 16;
2073 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2074 return false;
2075 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002076 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002077 int aBits = SkFloatAs2sCompliment(compA);
2078 int bBits = SkFloatAs2sCompliment(compB);
2079 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002080}
2081
caryclarkb4216502015-03-02 13:02:34 -08002082static bool approximately_zero_when_compared_to(double x, double y) {
2083 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002084}
2085
caryclarkb4216502015-03-02 13:02:34 -08002086
reed@google.com04863fa2011-05-15 04:08:24 +00002087// only valid for a single contour
2088struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002089 Convexicator()
2090 : fPtCount(0)
2091 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002092 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002093 , fIsFinite(true)
2094 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002095 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002096 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002097 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002098 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002099 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002100 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002101
2102 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002103 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002104 }
2105
2106 SkPath::Convexity getConvexity() const { return fConvexity; }
2107
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002108 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002109 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002110
reed@google.com04863fa2011-05-15 04:08:24 +00002111 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002112 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002113 return;
2114 }
2115
2116 if (0 == fPtCount) {
2117 fCurrPt = pt;
2118 ++fPtCount;
2119 } else {
2120 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002121 SkScalar lengthSqd = vec.lengthSqd();
2122 if (!SkScalarIsFinite(lengthSqd)) {
2123 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002124 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002125 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002126 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002127 fCurrPt = pt;
2128 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002129 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002130 } else {
2131 SkASSERT(fPtCount > 2);
2132 this->addVec(vec);
2133 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002134
reed@google.com85b6e392011-05-15 20:25:17 +00002135 int sx = sign(vec.fX);
2136 int sy = sign(vec.fY);
2137 fDx += (sx != fSx);
2138 fDy += (sy != fSy);
2139 fSx = sx;
2140 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002141
reed@google.com85b6e392011-05-15 20:25:17 +00002142 if (fDx > 3 || fDy > 3) {
2143 fConvexity = SkPath::kConcave_Convexity;
2144 }
reed@google.com04863fa2011-05-15 04:08:24 +00002145 }
2146 }
2147 }
2148
2149 void close() {
2150 if (fPtCount > 2) {
2151 this->addVec(fFirstVec);
2152 }
2153 }
2154
caryclarkb4216502015-03-02 13:02:34 -08002155 DirChange directionChange(const SkVector& curVec) {
2156 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2157
2158 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2159 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2160 largest = SkTMax(largest, -smallest);
2161
2162 if (!almost_equal(largest, largest + cross)) {
2163 int sign = SkScalarSignAsInt(cross);
2164 if (sign) {
2165 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2166 }
2167 }
2168
2169 if (cross) {
2170 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2171 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2172 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2173 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2174 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2175 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2176 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2177 if (sign) {
2178 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2179 }
2180 }
2181 }
2182
2183 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2184 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2185 fLastVec.dot(curVec) < 0.0f) {
2186 return kBackwards_DirChange;
2187 }
2188
2189 return kStraight_DirChange;
2190 }
2191
2192
caryclarkd3d1a982014-12-08 04:57:38 -08002193 bool isFinite() const {
2194 return fIsFinite;
2195 }
2196
caryclark5ccef572015-03-02 10:07:56 -08002197 void setCurve(bool isCurve) {
2198 fIsCurve = isCurve;
2199 }
2200
reed@google.com04863fa2011-05-15 04:08:24 +00002201private:
2202 void addVec(const SkVector& vec) {
2203 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002204 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002205 switch (dir) {
2206 case kLeft_DirChange: // fall through
2207 case kRight_DirChange:
2208 if (kInvalid_DirChange == fExpectedDir) {
2209 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002210 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2211 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002212 } else if (dir != fExpectedDir) {
2213 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002214 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002215 }
2216 fLastVec = vec;
2217 break;
2218 case kStraight_DirChange:
2219 break;
2220 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002221 if (fIsCurve) {
2222 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002223 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002224 }
robertphillipsc506e302014-09-16 09:43:31 -07002225 fLastVec = vec;
2226 break;
2227 case kInvalid_DirChange:
2228 SkFAIL("Use of invalid direction change flag");
2229 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002230 }
2231 }
2232
caryclarkb4216502015-03-02 13:02:34 -08002233 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002234 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002235 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002236 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2237 // value with the current vec is deemed to be of a significant value.
2238 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002239 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002240 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002241 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002242 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002243 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002244 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002245 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002246};
2247
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002248SkPath::Convexity SkPath::internalGetConvexity() const {
2249 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002250 SkPoint pts[4];
2251 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002252 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002253
2254 int contourCount = 0;
2255 int count;
2256 Convexicator state;
2257
caryclarkd3d1a982014-12-08 04:57:38 -08002258 if (!isFinite()) {
2259 return kUnknown_Convexity;
2260 }
caryclarke8c56662015-07-14 11:19:26 -07002261 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002262 switch (verb) {
2263 case kMove_Verb:
2264 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002265 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 return kConcave_Convexity;
2267 }
2268 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002269 // fall through
2270 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002271 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002272 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002273 break;
caryclark5ccef572015-03-02 10:07:56 -08002274 case kQuad_Verb:
2275 // fall through
2276 case kConic_Verb:
2277 // fall through
2278 case kCubic_Verb:
2279 count = 2 + (kCubic_Verb == verb);
2280 // As an additional enhancement, this could set curve true only
2281 // if the curve is nonlinear
2282 state.setCurve(true);
2283 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002285 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002286 state.close();
2287 count = 0;
2288 break;
2289 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002290 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002291 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002292 return kConcave_Convexity;
2293 }
2294
2295 for (int i = 1; i <= count; i++) {
2296 state.addPt(pts[i]);
2297 }
2298 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002299 if (!state.isFinite()) {
2300 return kUnknown_Convexity;
2301 }
reed@google.com04863fa2011-05-15 04:08:24 +00002302 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002303 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002304 return kConcave_Convexity;
2305 }
2306 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002307 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002308 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2309 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002310 }
2311 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002312}
reed@google.com69a99432012-01-10 18:00:10 +00002313
2314///////////////////////////////////////////////////////////////////////////////
2315
2316class ContourIter {
2317public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002318 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002319
2320 bool done() const { return fDone; }
2321 // if !done() then these may be called
2322 int count() const { return fCurrPtCount; }
2323 const SkPoint* pts() const { return fCurrPt; }
2324 void next();
2325
2326private:
2327 int fCurrPtCount;
2328 const SkPoint* fCurrPt;
2329 const uint8_t* fCurrVerb;
2330 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002331 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002332 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002333 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002334};
2335
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002336ContourIter::ContourIter(const SkPathRef& pathRef) {
2337 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002338 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002339 fCurrPt = pathRef.points();
2340 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002341 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002342 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002343 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002344 this->next();
2345}
2346
2347void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002348 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002349 fDone = true;
2350 }
2351 if (fDone) {
2352 return;
2353 }
2354
2355 // skip pts of prev contour
2356 fCurrPt += fCurrPtCount;
2357
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002358 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002359 int ptCount = 1; // moveTo
2360 const uint8_t* verbs = fCurrVerb;
2361
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002362 for (--verbs; verbs > fStopVerbs; --verbs) {
2363 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002364 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002365 goto CONTOUR_END;
2366 case SkPath::kLine_Verb:
2367 ptCount += 1;
2368 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002369 case SkPath::kConic_Verb:
2370 fCurrConicWeight += 1;
2371 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002372 case SkPath::kQuad_Verb:
2373 ptCount += 2;
2374 break;
2375 case SkPath::kCubic_Verb:
2376 ptCount += 3;
2377 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002378 case SkPath::kClose_Verb:
2379 break;
2380 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002381 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002382 break;
2383 }
2384 }
2385CONTOUR_END:
2386 fCurrPtCount = ptCount;
2387 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002388 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002389}
2390
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002391// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002392static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002393 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2394 // We may get 0 when the above subtracts underflow. We expect this to be
2395 // very rare and lazily promote to double.
2396 if (0 == cross) {
2397 double p0x = SkScalarToDouble(p0.fX);
2398 double p0y = SkScalarToDouble(p0.fY);
2399
2400 double p1x = SkScalarToDouble(p1.fX);
2401 double p1y = SkScalarToDouble(p1.fY);
2402
2403 double p2x = SkScalarToDouble(p2.fX);
2404 double p2y = SkScalarToDouble(p2.fY);
2405
2406 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2407 (p1y - p0y) * (p2x - p0x));
2408
2409 }
2410 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002411}
2412
reed@google.comc1ea60a2012-01-31 15:15:36 +00002413// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002414static int find_max_y(const SkPoint pts[], int count) {
2415 SkASSERT(count > 0);
2416 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002417 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002418 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002419 SkScalar y = pts[i].fY;
2420 if (y > max) {
2421 max = y;
2422 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002423 }
2424 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002425 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002426}
2427
reed@google.comcabaf1d2012-01-11 21:03:05 +00002428static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2429 int i = index;
2430 for (;;) {
2431 i = (i + inc) % n;
2432 if (i == index) { // we wrapped around, so abort
2433 break;
2434 }
2435 if (pts[index] != pts[i]) { // found a different point, success!
2436 break;
2437 }
2438 }
2439 return i;
2440}
2441
reed@google.comc1ea60a2012-01-31 15:15:36 +00002442/**
2443 * Starting at index, and moving forward (incrementing), find the xmin and
2444 * xmax of the contiguous points that have the same Y.
2445 */
2446static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2447 int* maxIndexPtr) {
2448 const SkScalar y = pts[index].fY;
2449 SkScalar min = pts[index].fX;
2450 SkScalar max = min;
2451 int minIndex = index;
2452 int maxIndex = index;
2453 for (int i = index + 1; i < n; ++i) {
2454 if (pts[i].fY != y) {
2455 break;
2456 }
2457 SkScalar x = pts[i].fX;
2458 if (x < min) {
2459 min = x;
2460 minIndex = i;
2461 } else if (x > max) {
2462 max = x;
2463 maxIndex = i;
2464 }
2465 }
2466 *maxIndexPtr = maxIndex;
2467 return minIndex;
2468}
2469
reed026beb52015-06-10 14:23:15 -07002470static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2471 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002472}
2473
reed@google.comac8543f2012-01-30 20:51:25 +00002474/*
2475 * We loop through all contours, and keep the computed cross-product of the
2476 * contour that contained the global y-max. If we just look at the first
2477 * contour, we may find one that is wound the opposite way (correctly) since
2478 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2479 * that is outer most (or at least has the global y-max) before we can consider
2480 * its cross product.
2481 */
reed026beb52015-06-10 14:23:15 -07002482bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002483 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2484 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002485 return true;
2486 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002487
2488 // don't want to pay the cost for computing this if it
2489 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002490 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2491 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002492 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002493 return false;
2494 }
reed@google.com69a99432012-01-10 18:00:10 +00002495
reed026beb52015-06-10 14:23:15 -07002496 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002497
reed@google.comac8543f2012-01-30 20:51:25 +00002498 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002499 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002500 SkScalar ymaxCross = 0;
2501
reed@google.com69a99432012-01-10 18:00:10 +00002502 for (; !iter.done(); iter.next()) {
2503 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002504 if (n < 3) {
2505 continue;
2506 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002507
reed@google.comcabaf1d2012-01-11 21:03:05 +00002508 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002509 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002510 int index = find_max_y(pts, n);
2511 if (pts[index].fY < ymax) {
2512 continue;
2513 }
2514
2515 // If there is more than 1 distinct point at the y-max, we take the
2516 // x-min and x-max of them and just subtract to compute the dir.
2517 if (pts[(index + 1) % n].fY == pts[index].fY) {
2518 int maxIndex;
2519 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2520 if (minIndex == maxIndex) {
2521 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002522 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002523 SkASSERT(pts[minIndex].fY == pts[index].fY);
2524 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2525 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2526 // we just subtract the indices, and let that auto-convert to
2527 // SkScalar, since we just want - or + to signal the direction.
2528 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002529 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002530 TRY_CROSSPROD:
2531 // Find a next and prev index to use for the cross-product test,
2532 // but we try to find pts that form non-zero vectors from pts[index]
2533 //
2534 // Its possible that we can't find two non-degenerate vectors, so
2535 // we have to guard our search (e.g. all the pts could be in the
2536 // same place).
2537
2538 // we pass n - 1 instead of -1 so we don't foul up % operator by
2539 // passing it a negative LH argument.
2540 int prev = find_diff_pt(pts, index, n, n - 1);
2541 if (prev == index) {
2542 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002543 continue;
2544 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002545 int next = find_diff_pt(pts, index, n, 1);
2546 SkASSERT(next != index);
2547 cross = cross_prod(pts[prev], pts[index], pts[next]);
2548 // if we get a zero and the points are horizontal, then we look at the spread in
2549 // x-direction. We really should continue to walk away from the degeneracy until
2550 // there is a divergence.
2551 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2552 // construct the subtract so we get the correct Direction below
2553 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002554 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002555 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002556
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002557 if (cross) {
2558 // record our best guess so far
2559 ymax = pts[index].fY;
2560 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002561 }
2562 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002563 if (ymaxCross) {
2564 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002565 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002566 return true;
2567 } else {
2568 return false;
2569 }
reed@google.comac8543f2012-01-30 20:51:25 +00002570}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002571
2572///////////////////////////////////////////////////////////////////////////////
2573
2574static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2575 SkScalar D, SkScalar t) {
2576 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2577}
2578
2579static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2580 SkScalar t) {
2581 SkScalar A = c3 + 3*(c1 - c2) - c0;
2582 SkScalar B = 3*(c2 - c1 - c1 + c0);
2583 SkScalar C = 3*(c1 - c0);
2584 SkScalar D = c0;
2585 return eval_cubic_coeff(A, B, C, D, t);
2586}
2587
2588/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2589 t value such that cubic(t) = target
2590 */
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002591static void chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002592 SkScalar target, SkScalar* t) {
2593 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2594 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002595
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002596 SkScalar D = c0 - target;
2597 SkScalar A = c3 + 3*(c1 - c2) - c0;
2598 SkScalar B = 3*(c2 - c1 - c1 + c0);
2599 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002600
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002601 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2602 SkScalar minT = 0;
2603 SkScalar maxT = SK_Scalar1;
2604 SkScalar mid;
2605 int i;
2606 for (i = 0; i < 16; i++) {
2607 mid = SkScalarAve(minT, maxT);
2608 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2609 if (delta < 0) {
2610 minT = mid;
2611 delta = -delta;
2612 } else {
2613 maxT = mid;
2614 }
2615 if (delta < TOLERANCE) {
2616 break;
2617 }
2618 }
2619 *t = mid;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002620}
2621
2622template <size_t N> static void find_minmax(const SkPoint pts[],
2623 SkScalar* minPtr, SkScalar* maxPtr) {
2624 SkScalar min, max;
2625 min = max = pts[0].fX;
2626 for (size_t i = 1; i < N; ++i) {
2627 min = SkMinScalar(min, pts[i].fX);
2628 max = SkMaxScalar(max, pts[i].fX);
2629 }
2630 *minPtr = min;
2631 *maxPtr = max;
2632}
2633
2634static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2635 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002636
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002637 int dir = 1;
2638 if (pts[0].fY > pts[3].fY) {
2639 storage[0] = pts[3];
2640 storage[1] = pts[2];
2641 storage[2] = pts[1];
2642 storage[3] = pts[0];
2643 pts = storage;
2644 dir = -1;
2645 }
2646 if (y < pts[0].fY || y >= pts[3].fY) {
2647 return 0;
2648 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002649
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002650 // quickreject or quickaccept
2651 SkScalar min, max;
2652 find_minmax<4>(pts, &min, &max);
2653 if (x < min) {
2654 return 0;
2655 }
2656 if (x > max) {
2657 return dir;
2658 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002659
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002660 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002661 SkScalar t;
2662 chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t);
2663 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 +00002664 return xt < x ? dir : 0;
2665}
2666
2667static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2668 SkPoint dst[10];
2669 int n = SkChopCubicAtYExtrema(pts, dst);
2670 int w = 0;
2671 for (int i = 0; i <= n; ++i) {
2672 w += winding_mono_cubic(&dst[i * 3], x, y);
2673 }
2674 return w;
2675}
2676
2677static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2678 SkScalar y0 = pts[0].fY;
2679 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002680
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002681 int dir = 1;
2682 if (y0 > y2) {
2683 SkTSwap(y0, y2);
2684 dir = -1;
2685 }
2686 if (y < y0 || y >= y2) {
2687 return 0;
2688 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002689
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002690 // bounds check on X (not required. is it faster?)
2691#if 0
2692 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2693 return 0;
2694 }
2695#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002696
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002697 SkScalar roots[2];
2698 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2699 2 * (pts[1].fY - pts[0].fY),
2700 pts[0].fY - y,
2701 roots);
2702 SkASSERT(n <= 1);
2703 SkScalar xt;
2704 if (0 == n) {
2705 SkScalar mid = SkScalarAve(y0, y2);
2706 // Need [0] and [2] if dir == 1
2707 // and [2] and [0] if dir == -1
2708 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2709 } else {
2710 SkScalar t = roots[0];
2711 SkScalar C = pts[0].fX;
2712 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2713 SkScalar B = 2 * (pts[1].fX - C);
2714 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2715 }
2716 return xt < x ? dir : 0;
2717}
2718
2719static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2720 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2721 if (y0 == y1) {
2722 return true;
2723 }
2724 if (y0 < y1) {
2725 return y1 <= y2;
2726 } else {
2727 return y1 >= y2;
2728 }
2729}
2730
2731static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2732 SkPoint dst[5];
2733 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002734
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002735 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2736 n = SkChopQuadAtYExtrema(pts, dst);
2737 pts = dst;
2738 }
2739 int w = winding_mono_quad(pts, x, y);
2740 if (n > 0) {
2741 w += winding_mono_quad(&pts[2], x, y);
2742 }
2743 return w;
2744}
2745
2746static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2747 SkScalar x0 = pts[0].fX;
2748 SkScalar y0 = pts[0].fY;
2749 SkScalar x1 = pts[1].fX;
2750 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002751
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002752 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002753
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002754 int dir = 1;
2755 if (y0 > y1) {
2756 SkTSwap(y0, y1);
2757 dir = -1;
2758 }
2759 if (y < y0 || y >= y1) {
2760 return 0;
2761 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2764 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002765
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002766 if (SkScalarSignAsInt(cross) == dir) {
2767 dir = 0;
2768 }
2769 return dir;
2770}
2771
reed@google.com4db592c2013-10-30 17:39:43 +00002772static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2773 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2774}
2775
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002776bool SkPath::contains(SkScalar x, SkScalar y) const {
2777 bool isInverse = this->isInverseFillType();
2778 if (this->isEmpty()) {
2779 return isInverse;
2780 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002781
reed@google.com4db592c2013-10-30 17:39:43 +00002782 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783 return isInverse;
2784 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 SkPath::Iter iter(*this, true);
2787 bool done = false;
2788 int w = 0;
2789 do {
2790 SkPoint pts[4];
2791 switch (iter.next(pts, false)) {
2792 case SkPath::kMove_Verb:
2793 case SkPath::kClose_Verb:
2794 break;
2795 case SkPath::kLine_Verb:
2796 w += winding_line(pts, x, y);
2797 break;
2798 case SkPath::kQuad_Verb:
2799 w += winding_quad(pts, x, y);
2800 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002801 case SkPath::kConic_Verb:
2802 SkASSERT(0);
2803 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 case SkPath::kCubic_Verb:
2805 w += winding_cubic(pts, x, y);
2806 break;
2807 case SkPath::kDone_Verb:
2808 done = true;
2809 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002810 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002811 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002812
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 switch (this->getFillType()) {
2814 case SkPath::kEvenOdd_FillType:
2815 case SkPath::kInverseEvenOdd_FillType:
2816 w &= 1;
2817 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002818 default:
2819 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 }
2821 return SkToBool(w);
2822}