blob: 4c148c5e4a3aea0b08d2d49eaef9c2783c15751c [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"
caryclark9aacd902015-12-14 08:38:09 -08009#include "SkCubicClipper.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000010#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080011#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070013#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700141 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700170 // Simulate fFirstDirection = that.fFirstDirection;
171 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700172 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173}
174
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000175bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000176 // note: don't need to look at isConvex or bounds, since just comparing the
177 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000178 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000179 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700184 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700188 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
189 uint8_t temp = fFirstDirection;
190 fFirstDirection.store(that.fFirstDirection.load());
191 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700192 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000196static inline bool check_edge_against_rect(const SkPoint& p0,
197 const SkPoint& p1,
198 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700199 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000200 const SkPoint* edgeBegin;
201 SkVector v;
reed026beb52015-06-10 14:23:15 -0700202 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000203 v = p1 - p0;
204 edgeBegin = &p0;
205 } else {
206 v = p0 - p1;
207 edgeBegin = &p1;
208 }
209 if (v.fX || v.fY) {
210 // check the cross product of v with the vec from edgeBegin to each rect corner
211 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
212 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
213 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
214 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
215 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
216 return false;
217 }
218 }
219 return true;
220}
221
222bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
223 // This only handles non-degenerate convex paths currently.
224 if (kConvex_Convexity != this->getConvexity()) {
225 return false;
226 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000227
reed026beb52015-06-10 14:23:15 -0700228 SkPathPriv::FirstDirection direction;
229 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000230 return false;
231 }
232
233 SkPoint firstPt;
234 SkPoint prevPt;
235 RawIter iter(*this);
236 SkPath::Verb verb;
237 SkPoint pts[4];
238 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000239 SkDEBUGCODE(int segmentCount = 0;)
240 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000241
242 while ((verb = iter.next(pts)) != kDone_Verb) {
243 int nextPt = -1;
244 switch (verb) {
245 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000246 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247 SkDEBUGCODE(++moveCnt);
248 firstPt = prevPt = pts[0];
249 break;
250 case kLine_Verb:
251 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000252 SkASSERT(moveCnt && !closeCount);
253 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 break;
255 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000256 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(moveCnt && !closeCount);
258 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000259 nextPt = 2;
260 break;
261 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkASSERT(moveCnt && !closeCount);
263 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000264 nextPt = 3;
265 break;
266 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000267 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 break;
269 default:
270 SkDEBUGFAIL("unknown verb");
271 }
272 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800273 if (SkPath::kConic_Verb == verb) {
274 SkConic orig;
275 orig.set(pts, iter.conicWeight());
276 SkPoint quadPts[5];
277 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
278 SK_ALWAYSBREAK(2 == count);
279
280 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
281 return false;
282 }
283 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
284 return false;
285 }
286 } else {
287 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
288 return false;
289 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 }
291 prevPt = pts[nextPt];
292 }
293 }
294
295 return check_edge_against_rect(prevPt, firstPt, rect, direction);
296}
297
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000298uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000299 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800300#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
302 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
303#endif
304 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000305}
djsollen@google.come63793a2012-03-21 15:39:03 +0000306
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307void SkPath::reset() {
308 SkDEBUGCODE(this->validate();)
309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000310 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000311 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312}
313
314void SkPath::rewind() {
315 SkDEBUGCODE(this->validate();)
316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000317 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000318 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319}
320
fsb1475b02016-01-20 09:51:07 -0800321bool SkPath::isLastContourClosed() const {
322 int verbCount = fPathRef->countVerbs();
323 if (0 == verbCount) {
324 return false;
325 }
326 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
327}
328
reed@google.com7e6c4d12012-05-10 14:05:43 +0000329bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000330 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000331
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000332 if (2 == verbCount) {
333 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
334 if (kLine_Verb == fPathRef->atVerb(1)) {
335 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000336 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000337 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000338 line[0] = pts[0];
339 line[1] = pts[1];
340 }
341 return true;
342 }
343 }
344 return false;
345}
346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347/*
348 Determines if path is a rect by keeping track of changes in direction
349 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000350
caryclark@google.comf1316942011-07-26 19:54:45 +0000351 The direction is computed such that:
352 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000353 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000354 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000355 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000356
caryclark@google.comf1316942011-07-26 19:54:45 +0000357A rectangle cycles up/right/down/left or up/left/down/right.
358
359The test fails if:
360 The path is closed, and followed by a line.
361 A second move creates a new endpoint.
362 A diagonal line is parsed.
363 There's more than four changes of direction.
364 There's a discontinuity on the line (e.g., a move in the middle)
365 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000366 The path contains a quadratic or cubic.
367 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000368 *The rectangle doesn't complete a cycle.
369 *The final point isn't equal to the first point.
370
371 *These last two conditions we relax if we have a 3-edge path that would
372 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374It's OK if the path has:
375 Several colinear line segments composing a rectangle side.
376 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378The direction takes advantage of the corners found since opposite sides
379must travel in opposite directions.
380
381FIXME: Allow colinear quads and cubics to be treated like lines.
382FIXME: If the API passes fill-only, return true if the filled stroke
383 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000384
385 first,last,next direction state-machine:
386 0x1 is set if the segment is horizontal
387 0x2 is set if the segment is moving to the right or down
388 thus:
389 two directions are opposites iff (dirA ^ dirB) == 0x2
390 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000391
caryclark@google.comf1316942011-07-26 19:54:45 +0000392 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000393static int rect_make_dir(SkScalar dx, SkScalar dy) {
394 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
395}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000396bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
397 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 int corners = 0;
399 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000400 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700401 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000402 first.set(0, 0);
403 last.set(0, 0);
404 int firstDirection = 0;
405 int lastDirection = 0;
406 int nextDirection = 0;
407 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700409 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000410 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000411 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700412 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
413 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000415 savePts = pts;
416 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000417 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700418 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 case kLine_Verb: {
420 SkScalar left = last.fX;
421 SkScalar top = last.fY;
422 SkScalar right = pts->fX;
423 SkScalar bottom = pts->fY;
424 ++pts;
425 if (left != right && top != bottom) {
426 return false; // diagonal
427 }
428 if (left == right && top == bottom) {
429 break; // single point on side OK
430 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000431 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000432 if (0 == corners) {
433 firstDirection = nextDirection;
434 first = last;
435 last = pts[-1];
436 corners = 1;
437 closedOrMoved = false;
438 break;
439 }
440 if (closedOrMoved) {
441 return false; // closed followed by a line
442 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000443 if (autoClose && nextDirection == firstDirection) {
444 break; // colinear with first
445 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 closedOrMoved = autoClose;
447 if (lastDirection != nextDirection) {
448 if (++corners > 4) {
449 return false; // too many direction changes
450 }
451 }
452 last = pts[-1];
453 if (lastDirection == nextDirection) {
454 break; // colinear segment
455 }
456 // Possible values for corners are 2, 3, and 4.
457 // When corners == 3, nextDirection opposes firstDirection.
458 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000459 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
461 if ((directionCycle ^ turn) != nextDirection) {
462 return false; // direction didn't follow cycle
463 }
464 break;
465 }
466 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000467 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 case kCubic_Verb:
469 return false; // quadratic, cubic not allowed
470 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700471 if (allowPartial && !autoClose && firstDirection) {
472 insertClose = true;
473 *currVerb -= 1; // try move again afterwards
474 goto addMissingClose;
475 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 last = *pts++;
477 closedOrMoved = true;
478 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000479 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000480 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000481 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000483 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700485addMissingClose:
486 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 }
488 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000489 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000490 if (!result) {
491 // check if we are just an incomplete rectangle, in which case we can
492 // return true, but not claim to be closed.
493 // e.g.
494 // 3 sided rectangle
495 // 4 sided but the last edge is not long enough to reach the start
496 //
497 SkScalar closeX = first.x() - last.x();
498 SkScalar closeY = first.y() - last.y();
499 if (closeX && closeY) {
500 return false; // we're diagonal, abort (can we ever reach this?)
501 }
502 int closeDirection = rect_make_dir(closeX, closeY);
503 // make sure the close-segment doesn't double-back on itself
504 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
505 result = true;
506 autoClose = false; // we are not closed
507 }
508 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000509 if (savePts) {
510 *ptsPtr = savePts;
511 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000512 if (result && isClosed) {
513 *isClosed = autoClose;
514 }
515 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000516 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000517 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000518 return result;
519}
520
robertphillips4f662e62014-12-29 14:06:51 -0800521bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000522 SkDEBUGCODE(this->validate();)
523 int currVerb = 0;
524 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800525 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800526 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800527 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000528 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800529 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800530 int32_t num = SkToS32(pts - first);
531 if (num) {
532 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800533 } else {
534 // 'pts' isn't updated for open rects
535 *rect = this->getBounds();
536 }
537 }
538 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000539}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000540
caryclark95bc5f32015-04-08 08:34:15 -0700541bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000542 SkDEBUGCODE(this->validate();)
543 int currVerb = 0;
544 const SkPoint* pts = fPathRef->points();
545 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000546 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700547 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return false;
549 }
550 const SkPoint* last = pts;
551 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700552 bool isClosed;
553 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000554 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700555 if (!isClosed) {
556 pts = fPathRef->points() + fPathRef->countPoints();
557 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000558 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 if (testRects[0].contains(testRects[1])) {
560 if (rects) {
561 rects[0] = testRects[0];
562 rects[1] = testRects[1];
563 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000564 if (dirs) {
565 dirs[0] = testDirs[0];
566 dirs[1] = testDirs[1];
567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 return true;
569 }
570 if (testRects[1].contains(testRects[0])) {
571 if (rects) {
572 rects[0] = testRects[1];
573 rects[1] = testRects[0];
574 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000575 if (dirs) {
576 dirs[0] = testDirs[1];
577 dirs[1] = testDirs[0];
578 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000579 return true;
580 }
581 }
582 return false;
583}
584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585int SkPath::countPoints() const {
586 return fPathRef->countPoints();
587}
588
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000589int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000590 SkDEBUGCODE(this->validate();)
591
592 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000593 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800595 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000596 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597}
598
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000599SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000600 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
601 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000602 }
603 return SkPoint::Make(0, 0);
604}
605
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000606int SkPath::countVerbs() const {
607 return fPathRef->countVerbs();
608}
609
610static inline void copy_verbs_reverse(uint8_t* inorderDst,
611 const uint8_t* reversedSrc,
612 int count) {
613 for (int i = 0; i < count; ++i) {
614 inorderDst[i] = reversedSrc[~i];
615 }
616}
617
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000618int SkPath::getVerbs(uint8_t dst[], int max) const {
619 SkDEBUGCODE(this->validate();)
620
621 SkASSERT(max >= 0);
622 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 int count = SkMin32(max, fPathRef->countVerbs());
624 copy_verbs_reverse(dst, fPathRef->verbs(), count);
625 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000626}
627
reed@google.com294dd7b2011-10-11 11:58:32 +0000628bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 SkDEBUGCODE(this->validate();)
630
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000632 if (count > 0) {
633 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000636 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000638 if (lastPt) {
639 lastPt->set(0, 0);
640 }
641 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642}
643
caryclarkaec25102015-04-29 08:28:30 -0700644void SkPath::setPt(int index, SkScalar x, SkScalar y) {
645 SkDEBUGCODE(this->validate();)
646
647 int count = fPathRef->countPoints();
648 if (count <= index) {
649 return;
650 } else {
651 SkPathRef::Editor ed(&fPathRef);
652 ed.atPoint(index)->set(x, y);
653 }
654}
655
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656void SkPath::setLastPt(SkScalar x, SkScalar y) {
657 SkDEBUGCODE(this->validate();)
658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 if (count == 0) {
661 this->moveTo(x, y);
662 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 SkPathRef::Editor ed(&fPathRef);
664 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 }
666}
667
reed@google.com04863fa2011-05-15 04:08:24 +0000668void SkPath::setConvexity(Convexity c) {
669 if (fConvexity != c) {
670 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000671 }
672}
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674//////////////////////////////////////////////////////////////////////////////
675// Construction methods
676
reed026beb52015-06-10 14:23:15 -0700677#define DIRTY_AFTER_EDIT \
678 do { \
679 fConvexity = kUnknown_Convexity; \
680 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000681 } while (0)
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683void SkPath::incReserve(U16CPU inc) {
684 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkDEBUGCODE(this->validate();)
687}
688
689void SkPath::moveTo(SkScalar x, SkScalar y) {
690 SkDEBUGCODE(this->validate();)
691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000694 // remember our index
695 fLastMoveToIndex = fPathRef->countPoints();
696
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000697 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700698
699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rMoveTo(SkScalar x, SkScalar y) {
703 SkPoint pt;
704 this->getLastPt(&pt);
705 this->moveTo(pt.fX + x, pt.fY + y);
706}
707
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000708void SkPath::injectMoveToIfNeeded() {
709 if (fLastMoveToIndex < 0) {
710 SkScalar x, y;
711 if (fPathRef->countVerbs() == 0) {
712 x = y = 0;
713 } else {
714 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
715 x = pt.fX;
716 y = pt.fY;
717 }
718 this->moveTo(x, y);
719 }
720}
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722void SkPath::lineTo(SkScalar x, SkScalar y) {
723 SkDEBUGCODE(this->validate();)
724
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000725 this->injectMoveToIfNeeded();
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 SkPathRef::Editor ed(&fPathRef);
728 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729
reed@google.comb54455e2011-05-16 14:16:04 +0000730 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731}
732
733void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000734 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->lineTo(pt.fX + x, pt.fY + y);
738}
739
740void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000743 this->injectMoveToIfNeeded();
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000749
reed@google.comb54455e2011-05-16 14:16:04 +0000750 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751}
752
753void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000754 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755 SkPoint pt;
756 this->getLastPt(&pt);
757 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
758}
759
reed@google.com277c3f82013-05-31 15:17:50 +0000760void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
761 SkScalar w) {
762 // check for <= 0 or NaN with this test
763 if (!(w > 0)) {
764 this->lineTo(x2, y2);
765 } else if (!SkScalarIsFinite(w)) {
766 this->lineTo(x1, y1);
767 this->lineTo(x2, y2);
768 } else if (SK_Scalar1 == w) {
769 this->quadTo(x1, y1, x2, y2);
770 } else {
771 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 this->injectMoveToIfNeeded();
774
reed@google.com277c3f82013-05-31 15:17:50 +0000775 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000776 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000777 pts[0].set(x1, y1);
778 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000779
reed@google.com277c3f82013-05-31 15:17:50 +0000780 DIRTY_AFTER_EDIT;
781 }
782}
783
784void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
785 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000786 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000787 SkPoint pt;
788 this->getLastPt(&pt);
789 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
790}
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
793 SkScalar x3, SkScalar y3) {
794 SkDEBUGCODE(this->validate();)
795
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000796 this->injectMoveToIfNeeded();
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 SkPathRef::Editor ed(&fPathRef);
799 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 pts[0].set(x1, y1);
801 pts[1].set(x2, y2);
802 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803
reed@google.comb54455e2011-05-16 14:16:04 +0000804 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805}
806
807void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
808 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000809 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 SkPoint pt;
811 this->getLastPt(&pt);
812 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
813 pt.fX + x3, pt.fY + y3);
814}
815
816void SkPath::close() {
817 SkDEBUGCODE(this->validate();)
818
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000819 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 case kLine_Verb:
823 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000824 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000826 case kMove_Verb: {
827 SkPathRef::Editor ed(&fPathRef);
828 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000830 }
reed@google.com277c3f82013-05-31 15:17:50 +0000831 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000832 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000833 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000834 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000835 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000836 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 }
838 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839
840 // signal that we need a moveTo to follow us (unless we're done)
841#if 0
842 if (fLastMoveToIndex >= 0) {
843 fLastMoveToIndex = ~fLastMoveToIndex;
844 }
845#else
846 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
847#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848}
849
850///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000851
fmalitac08d53e2015-11-17 09:53:29 -0800852namespace {
853
854template <unsigned N>
855class PointIterator {
856public:
857 PointIterator(SkPath::Direction dir, unsigned startIndex)
858 : fCurrent(startIndex % N)
859 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
860
861 const SkPoint& current() const {
862 SkASSERT(fCurrent < N);
863 return fPts[fCurrent];
864 }
865
866 const SkPoint& next() {
867 fCurrent = (fCurrent + fAdvance) % N;
868 return this->current();
869 }
870
871protected:
872 SkPoint fPts[N];
873
874private:
875 unsigned fCurrent;
876 unsigned fAdvance;
877};
878
879class RectPointIterator : public PointIterator<4> {
880public:
881 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
882 : PointIterator(dir, startIndex) {
883
884 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
885 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
886 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
887 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
888 }
889};
890
891class OvalPointIterator : public PointIterator<4> {
892public:
893 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
894 : PointIterator(dir, startIndex) {
895
896 const SkScalar cx = oval.centerX();
897 const SkScalar cy = oval.centerY();
898
899 fPts[0] = SkPoint::Make(cx, oval.fTop);
900 fPts[1] = SkPoint::Make(oval.fRight, cy);
901 fPts[2] = SkPoint::Make(cx, oval.fBottom);
902 fPts[3] = SkPoint::Make(oval.fLeft, cy);
903 }
904};
905
906class RRectPointIterator : public PointIterator<8> {
907public:
908 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
909 : PointIterator(dir, startIndex) {
910
911 const SkRect& bounds = rrect.getBounds();
912 const SkScalar L = bounds.fLeft;
913 const SkScalar T = bounds.fTop;
914 const SkScalar R = bounds.fRight;
915 const SkScalar B = bounds.fBottom;
916
917 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
918 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
919 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
920 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
921 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
922 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
923 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
924 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
925 }
926};
927
928} // anonymous namespace
929
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000930static void assert_known_direction(int dir) {
931 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
932}
933
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800935 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936}
937
938void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
939 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800940 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
941}
942
943void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000944 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700945 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800946 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000947 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800948 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000949
fmalitac08d53e2015-11-17 09:53:29 -0800950 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000951
fmalitac08d53e2015-11-17 09:53:29 -0800952 const int kVerbs = 5; // moveTo + 3x lineTo + close
953 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954
fmalitac08d53e2015-11-17 09:53:29 -0800955 RectPointIterator iter(rect, dir, startIndex);
956
957 this->moveTo(iter.current());
958 this->lineTo(iter.next());
959 this->lineTo(iter.next());
960 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800962
963 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964}
965
reed@google.com744faba2012-05-29 19:54:52 +0000966void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
967 SkDEBUGCODE(this->validate();)
968 if (count <= 0) {
969 return;
970 }
971
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000972 fLastMoveToIndex = fPathRef->countPoints();
973
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000974 // +close makes room for the extra kClose_Verb
975 SkPathRef::Editor ed(&fPathRef, count+close, count);
976
977 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000978 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000979 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
980 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000981 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000982
reed@google.com744faba2012-05-29 19:54:52 +0000983 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000984 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800985 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000986 }
987
reed@google.com744faba2012-05-29 19:54:52 +0000988 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000989 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000990}
991
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000992#include "SkGeometry.h"
993
reedf90ea012015-01-29 12:03:58 -0800994static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
995 SkPoint* pt) {
996 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000997 // Chrome uses this path to move into and out of ovals. If not
998 // treated as a special case the moves can distort the oval's
999 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001000 pt->set(oval.fRight, oval.centerY());
1001 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001002 } else if (0 == oval.width() && 0 == oval.height()) {
1003 // Chrome will sometimes create 0 radius round rects. Having degenerate
1004 // quad segments in the path prevents the path from being recognized as
1005 // a rect.
1006 // TODO: optimizing the case where only one of width or height is zero
1007 // should also be considered. This case, however, doesn't seem to be
1008 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001009 pt->set(oval.fRight, oval.fTop);
1010 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001011 }
reedf90ea012015-01-29 12:03:58 -08001012 return false;
1013}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001014
reedd5d27d92015-02-09 13:54:43 -08001015// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1016//
1017static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1018 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1019 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1020 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001021
1022 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001023 loss in radians-conversion and/or sin/cos, we may end up with coincident
1024 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1025 of drawing a nearly complete circle (good).
1026 e.g. canvas.drawArc(0, 359.99, ...)
1027 -vs- canvas.drawArc(0, 359.9, ...)
1028 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001029 */
reedd5d27d92015-02-09 13:54:43 -08001030 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001031 SkScalar sw = SkScalarAbs(sweepAngle);
1032 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1033 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1034 // make a guess at a tiny angle (in radians) to tweak by
1035 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1036 // not sure how much will be enough, so we use a loop
1037 do {
1038 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001039 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1040 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 }
1042 }
reedd5d27d92015-02-09 13:54:43 -08001043 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1044}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045
reed9e779d42015-02-17 11:43:14 -08001046/**
1047 * If this returns 0, then the caller should just line-to the singlePt, else it should
1048 * ignore singlePt and append the specified number of conics.
1049 */
reedd5d27d92015-02-09 13:54:43 -08001050static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001051 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1052 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001053 SkMatrix matrix;
1054
1055 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1056 matrix.postTranslate(oval.centerX(), oval.centerY());
1057
reed9e779d42015-02-17 11:43:14 -08001058 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1059 if (0 == count) {
1060 matrix.mapXY(start.x(), start.y(), singlePt);
1061 }
1062 return count;
reedd5d27d92015-02-09 13:54:43 -08001063}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001065void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001067 SkRRect rrect;
1068 rrect.setRectRadii(rect, (const SkVector*) radii);
1069 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070}
1071
reed@google.com4ed0fb72012-12-12 20:48:18 +00001072void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001073 // legacy start indices: 6 (CW) and 7(CCW)
1074 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1075}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001076
fmalitac08d53e2015-11-17 09:53:29 -08001077void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1078 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001079
fmalitac08d53e2015-11-17 09:53:29 -08001080 if (rrect.isEmpty()) {
1081 return;
reed1b28a3a2014-12-17 14:42:09 -08001082 }
fmalitac08d53e2015-11-17 09:53:29 -08001083
caryclarkda707bf2015-11-19 14:47:43 -08001084 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001085 const SkRect& bounds = rrect.getBounds();
1086
1087 if (rrect.isRect()) {
1088 // degenerate(rect) => radii points are collapsing
1089 this->addRect(bounds, dir, (startIndex + 1) / 2);
1090 } else if (rrect.isOval()) {
1091 // degenerate(oval) => line points are collapsing
1092 this->addOval(bounds, dir, startIndex / 2);
1093 } else {
1094 fFirstDirection = this->hasOnlyMoveTos() ?
1095 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1096
1097 SkAutoPathBoundsUpdate apbu(this, bounds);
1098 SkAutoDisableDirectionCheck addc(this);
1099
1100 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1101 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1102 const SkScalar weight = SK_ScalarRoot2Over2;
1103
1104 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1105 const int kVerbs = startsWithConic
1106 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1107 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1108 this->incReserve(kVerbs);
1109
1110 RRectPointIterator rrectIter(rrect, dir, startIndex);
1111 // Corner iterator indices follow the collapsed radii model,
1112 // adjusted such that the start pt is "behind" the radii start pt.
1113 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1114 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1115
1116 this->moveTo(rrectIter.current());
1117 if (startsWithConic) {
1118 for (unsigned i = 0; i < 3; ++i) {
1119 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1120 this->lineTo(rrectIter.next());
1121 }
1122 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1123 // final lineTo handled by close().
1124 } else {
1125 for (unsigned i = 0; i < 4; ++i) {
1126 this->lineTo(rrectIter.next());
1127 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1128 }
1129 }
1130 this->close();
1131
caryclarkda707bf2015-11-19 14:47:43 -08001132 SkPathRef::Editor ed(&fPathRef);
1133 ed.setIsRRect(isRRect);
1134
fmalitac08d53e2015-11-17 09:53:29 -08001135 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1136 }
1137
1138 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001139}
1140
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001141bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001142 int count = fPathRef->countVerbs();
1143 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1144 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001145 if (*verbs == kLine_Verb ||
1146 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001147 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001148 *verbs == kCubic_Verb) {
1149 return false;
1150 }
1151 ++verbs;
1152 }
1153 return true;
1154}
1155
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001156void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1157 Direction dir) {
1158 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001159
humper@google.com75e3ca12013-04-08 21:44:11 +00001160 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001161 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001162 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001163 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001164 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1165 return;
1166 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001167
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001168 SkRRect rrect;
1169 rrect.setRectXY(rect, rx, ry);
1170 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001171}
1172
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001174 // legacy start index: 1
1175 this->addOval(oval, dir, 1);
1176}
1177
1178void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001179 assert_known_direction(dir);
1180
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181 /* If addOval() is called after previous moveTo(),
1182 this path is still marked as an oval. This is used to
1183 fit into WebKit's calling sequences.
1184 We can't simply check isEmpty() in this case, as additional
1185 moveTo() would mark the path non empty.
1186 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001187 bool isOval = hasOnlyMoveTos();
1188 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001189 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001190 } else {
reed026beb52015-06-10 14:23:15 -07001191 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001192 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001193
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001194 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195 SkAutoPathBoundsUpdate apbu(this, oval);
1196
fmalitac08d53e2015-11-17 09:53:29 -08001197 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1198 const int kVerbs = 6; // moveTo + 4x conicTo + close
1199 this->incReserve(kVerbs);
1200
1201 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1202 // The corner iterator pts are tracking "behind" the oval/radii pts.
1203 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001204 const SkScalar weight = SK_ScalarRoot2Over2;
1205
fmalitac08d53e2015-11-17 09:53:29 -08001206 this->moveTo(ovalIter.current());
1207 for (unsigned i = 0; i < 4; ++i) {
1208 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001209 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211
fmalitac08d53e2015-11-17 09:53:29 -08001212 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1213
robertphillips@google.com466310d2013-12-03 16:43:54 +00001214 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001215
robertphillips@google.com466310d2013-12-03 16:43:54 +00001216 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001217}
1218
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1220 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001221 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 }
1223}
1224
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1226 bool forceMoveTo) {
1227 if (oval.width() < 0 || oval.height() < 0) {
1228 return;
1229 }
1230
reedf90ea012015-01-29 12:03:58 -08001231 if (fPathRef->countVerbs() == 0) {
1232 forceMoveTo = true;
1233 }
1234
1235 SkPoint lonePt;
1236 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1237 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1238 return;
1239 }
1240
reedd5d27d92015-02-09 13:54:43 -08001241 SkVector startV, stopV;
1242 SkRotationDirection dir;
1243 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1244
reed9e779d42015-02-17 11:43:14 -08001245 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001246 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001247 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001248 if (count) {
1249 this->incReserve(count * 2 + 1);
1250 const SkPoint& pt = conics[0].fPts[0];
1251 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1252 for (int i = 0; i < count; ++i) {
1253 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1254 }
reed9e779d42015-02-17 11:43:14 -08001255 } else {
1256 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001257 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258}
1259
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001260void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 if (oval.isEmpty() || 0 == sweepAngle) {
1262 return;
1263 }
1264
1265 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1266
1267 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1268 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001269 } else {
1270 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272}
1273
1274/*
1275 Need to handle the case when the angle is sharp, and our computed end-points
1276 for the arc go behind pt1 and/or p2...
1277*/
reedc7789042015-01-29 12:59:11 -08001278void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001279 if (radius == 0) {
1280 this->lineTo(x1, y1);
1281 return;
1282 }
1283
1284 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001285
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 // need to know our prev pt so we can construct tangent vectors
1287 {
1288 SkPoint start;
1289 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001290 // Handle degenerate cases by adding a line to the first point and
1291 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001292 before.setNormalize(x1 - start.fX, y1 - start.fY);
1293 after.setNormalize(x2 - x1, y2 - y1);
1294 }
reed@google.comabf15c12011-01-18 20:35:51 +00001295
reed@android.com8a1c16f2008-12-17 15:59:43 +00001296 SkScalar cosh = SkPoint::DotProduct(before, after);
1297 SkScalar sinh = SkPoint::CrossProduct(before, after);
1298
1299 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001300 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 return;
1302 }
reed@google.comabf15c12011-01-18 20:35:51 +00001303
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1305 if (dist < 0) {
1306 dist = -dist;
1307 }
1308
1309 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1310 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1311 SkRotationDirection arcDir;
1312
1313 // now turn before/after into normals
1314 if (sinh > 0) {
1315 before.rotateCCW();
1316 after.rotateCCW();
1317 arcDir = kCW_SkRotationDirection;
1318 } else {
1319 before.rotateCW();
1320 after.rotateCW();
1321 arcDir = kCCW_SkRotationDirection;
1322 }
1323
1324 SkMatrix matrix;
1325 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001326
reed@android.com8a1c16f2008-12-17 15:59:43 +00001327 matrix.setScale(radius, radius);
1328 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1329 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001330
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001332
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 this->incReserve(count);
1334 // [xx,yy] == pts[0]
1335 this->lineTo(xx, yy);
1336 for (int i = 1; i < count; i += 2) {
1337 this->quadTo(pts[i], pts[i+1]);
1338 }
1339}
1340
1341///////////////////////////////////////////////////////////////////////////////
1342
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001343void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 SkMatrix matrix;
1345
1346 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001347 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348}
1349
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001350void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001351 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001353 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 SkPoint pts[4];
1355 Verb verb;
1356
1357 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001358 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 while ((verb = iter.next(pts)) != kDone_Verb) {
1360 switch (verb) {
1361 case kMove_Verb:
1362 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001363 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1364 injectMoveToIfNeeded(); // In case last contour is closed
1365 this->lineTo(pts[0]);
1366 } else {
1367 this->moveTo(pts[0]);
1368 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 break;
1370 case kLine_Verb:
1371 proc(matrix, &pts[1], &pts[1], 1);
1372 this->lineTo(pts[1]);
1373 break;
1374 case kQuad_Verb:
1375 proc(matrix, &pts[1], &pts[1], 2);
1376 this->quadTo(pts[1], pts[2]);
1377 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001378 case kConic_Verb:
1379 proc(matrix, &pts[1], &pts[1], 2);
1380 this->conicTo(pts[1], pts[2], iter.conicWeight());
1381 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 case kCubic_Verb:
1383 proc(matrix, &pts[1], &pts[1], 3);
1384 this->cubicTo(pts[1], pts[2], pts[3]);
1385 break;
1386 case kClose_Verb:
1387 this->close();
1388 break;
1389 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001390 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001392 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393 }
1394}
1395
1396///////////////////////////////////////////////////////////////////////////////
1397
reed@google.com277c3f82013-05-31 15:17:50 +00001398static int pts_in_verb(unsigned verb) {
1399 static const uint8_t gPtsInVerb[] = {
1400 1, // kMove
1401 1, // kLine
1402 2, // kQuad
1403 2, // kConic
1404 3, // kCubic
1405 0, // kClose
1406 0 // kDone
1407 };
1408
1409 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1410 return gPtsInVerb[verb];
1411}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412
reed@android.com8a1c16f2008-12-17 15:59:43 +00001413// ignore the last point of the 1st contour
1414void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001415 int i, vcount = path.fPathRef->countVerbs();
1416 // exit early if the path is empty, or just has a moveTo.
1417 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001418 return;
1419 }
1420
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001421 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001423 const uint8_t* verbs = path.fPathRef->verbs();
1424 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001425 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001427 SkASSERT(verbs[~0] == kMove_Verb);
1428 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001429 unsigned v = verbs[~i];
1430 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 if (n == 0) {
1432 break;
1433 }
1434 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001435 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 }
1437
1438 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001439 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 case kLine_Verb:
1441 this->lineTo(pts[-1].fX, pts[-1].fY);
1442 break;
1443 case kQuad_Verb:
1444 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1445 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001446 case kConic_Verb:
1447 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1448 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 case kCubic_Verb:
1450 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1451 pts[-3].fX, pts[-3].fY);
1452 break;
1453 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001454 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 break;
1456 }
reed@google.com277c3f82013-05-31 15:17:50 +00001457 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 }
1459}
1460
reed@google.com63d73742012-01-10 15:33:12 +00001461void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001462 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 const SkPoint* pts = src.fPathRef->pointsEnd();
1465 // we will iterator through src's verbs backwards
1466 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1467 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001468 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001469
1470 bool needMove = true;
1471 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001472 while (verbs < verbsEnd) {
1473 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001474 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001475
1476 if (needMove) {
1477 --pts;
1478 this->moveTo(pts->fX, pts->fY);
1479 needMove = false;
1480 }
1481 pts -= n;
1482 switch (v) {
1483 case kMove_Verb:
1484 if (needClose) {
1485 this->close();
1486 needClose = false;
1487 }
1488 needMove = true;
1489 pts += 1; // so we see the point in "if (needMove)" above
1490 break;
1491 case kLine_Verb:
1492 this->lineTo(pts[0]);
1493 break;
1494 case kQuad_Verb:
1495 this->quadTo(pts[1], pts[0]);
1496 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001497 case kConic_Verb:
1498 this->conicTo(pts[1], pts[0], *--conicWeights);
1499 break;
reed@google.com63d73742012-01-10 15:33:12 +00001500 case kCubic_Verb:
1501 this->cubicTo(pts[2], pts[1], pts[0]);
1502 break;
1503 case kClose_Verb:
1504 needClose = true;
1505 break;
1506 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001507 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001508 }
1509 }
1510}
1511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512///////////////////////////////////////////////////////////////////////////////
1513
1514void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1515 SkMatrix matrix;
1516
1517 matrix.setTranslate(dx, dy);
1518 this->transform(matrix, dst);
1519}
1520
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1522 int level = 2) {
1523 if (--level >= 0) {
1524 SkPoint tmp[7];
1525
1526 SkChopCubicAtHalf(pts, tmp);
1527 subdivide_cubic_to(path, &tmp[0], level);
1528 subdivide_cubic_to(path, &tmp[3], level);
1529 } else {
1530 path->cubicTo(pts[1], pts[2], pts[3]);
1531 }
1532}
1533
1534void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1535 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001536 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 dst = (SkPath*)this;
1538 }
1539
tomhudson@google.com8d430182011-06-06 19:11:19 +00001540 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 SkPath tmp;
1542 tmp.fFillType = fFillType;
1543
1544 SkPath::Iter iter(*this, false);
1545 SkPoint pts[4];
1546 SkPath::Verb verb;
1547
reed@google.com4a3b7142012-05-16 17:16:46 +00001548 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 switch (verb) {
1550 case kMove_Verb:
1551 tmp.moveTo(pts[0]);
1552 break;
1553 case kLine_Verb:
1554 tmp.lineTo(pts[1]);
1555 break;
1556 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001557 // promote the quad to a conic
1558 tmp.conicTo(pts[1], pts[2],
1559 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001561 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001562 tmp.conicTo(pts[1], pts[2],
1563 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001564 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 case kCubic_Verb:
1566 subdivide_cubic_to(&tmp, pts);
1567 break;
1568 case kClose_Verb:
1569 tmp.close();
1570 break;
1571 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001572 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 break;
1574 }
1575 }
1576
1577 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001578 SkPathRef::Editor ed(&dst->fPathRef);
1579 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001580 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001582 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1583
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001586 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001587 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001589
reed026beb52015-06-10 14:23:15 -07001590 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1591 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001592 } else {
1593 SkScalar det2x2 =
1594 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1595 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1596 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001597 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1598 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001599 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001600 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001601 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001602 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001603 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001604 }
1605 }
1606
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 SkDEBUGCODE(dst->validate();)
1608 }
1609}
1610
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611///////////////////////////////////////////////////////////////////////////////
1612///////////////////////////////////////////////////////////////////////////////
1613
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001614enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001615 kEmptyContour_SegmentState, // The current contour is empty. We may be
1616 // starting processing or we may have just
1617 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001618 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1619 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1620 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621};
1622
1623SkPath::Iter::Iter() {
1624#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001625 fPts = nullptr;
1626 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001628 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001629 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630#endif
1631 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001632 fVerbs = nullptr;
1633 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634 fNeedClose = false;
1635}
1636
1637SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1638 this->setPath(path, forceClose);
1639}
1640
1641void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001642 fPts = path.fPathRef->points();
1643 fVerbs = path.fPathRef->verbs();
1644 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001645 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001646 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001647 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 fForceClose = SkToU8(forceClose);
1649 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001650 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651}
1652
1653bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001654 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 return false;
1656 }
1657 if (fForceClose) {
1658 return true;
1659 }
1660
1661 const uint8_t* verbs = fVerbs;
1662 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001664 if (kMove_Verb == *(verbs - 1)) {
1665 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 }
1667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 while (verbs > stop) {
1669 // verbs points one beyond the current verb, decrement first.
1670 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 if (kMove_Verb == v) {
1672 break;
1673 }
1674 if (kClose_Verb == v) {
1675 return true;
1676 }
1677 }
1678 return false;
1679}
1680
1681SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001682 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001684 // A special case: if both points are NaN, SkPoint::operation== returns
1685 // false, but the iterator expects that they are treated as the same.
1686 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001687 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1688 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001689 return kClose_Verb;
1690 }
1691
reed@google.com9e25dbf2012-05-15 17:05:38 +00001692 pts[0] = fLastPt;
1693 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 fLastPt = fMoveTo;
1695 fCloseLine = true;
1696 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001697 } else {
1698 pts[0] = fMoveTo;
1699 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701}
1702
reed@google.com9e25dbf2012-05-15 17:05:38 +00001703const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001704 if (fSegmentState == kAfterMove_SegmentState) {
1705 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001706 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001707 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001709 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1710 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001711 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713}
1714
caryclarke8c56662015-07-14 11:19:26 -07001715void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001716 // We need to step over anything that will not move the current draw point
1717 // forward before the next move is seen
1718 const uint8_t* lastMoveVerb = 0;
1719 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001720 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001721 SkPoint lastPt = fLastPt;
1722 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001723 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724 switch (verb) {
1725 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726 // Keep a record of this most recent move
1727 lastMoveVerb = fVerbs;
1728 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001729 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001730 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001731 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732 fPts++;
1733 break;
1734
1735 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001736 // A close when we are in a segment is always valid except when it
1737 // follows a move which follows a segment.
1738 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739 return;
1740 }
1741 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743 break;
1744
1745 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001746 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001747 if (lastMoveVerb) {
1748 fVerbs = lastMoveVerb;
1749 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001750 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001751 return;
1752 }
1753 return;
1754 }
1755 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001756 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fPts++;
1758 break;
1759
reed@google.com277c3f82013-05-31 15:17:50 +00001760 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001761 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001762 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001763 if (lastMoveVerb) {
1764 fVerbs = lastMoveVerb;
1765 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001766 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001767 return;
1768 }
1769 return;
1770 }
1771 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001772 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001774 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001775 break;
1776
1777 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001778 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001779 if (lastMoveVerb) {
1780 fVerbs = lastMoveVerb;
1781 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001782 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 return;
1784 }
1785 return;
1786 }
1787 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001789 fPts += 3;
1790 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001791
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001793 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794 }
1795 }
1796}
1797
reed@google.com4a3b7142012-05-16 17:16:46 +00001798SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001799 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001800
reed@android.com8a1c16f2008-12-17 15:59:43 +00001801 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 // Close the curve if requested and if there is some curve to close
1803 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001804 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 return kLine_Verb;
1806 }
1807 fNeedClose = false;
1808 return kClose_Verb;
1809 }
1810 return kDone_Verb;
1811 }
1812
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001813 // fVerbs is one beyond the current verb, decrement first
1814 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001815 const SkPoint* SK_RESTRICT srcPts = fPts;
1816 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817
1818 switch (verb) {
1819 case kMove_Verb:
1820 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001821 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 verb = this->autoClose(pts);
1823 if (verb == kClose_Verb) {
1824 fNeedClose = false;
1825 }
1826 return (Verb)verb;
1827 }
1828 if (fVerbs == fVerbStop) { // might be a trailing moveto
1829 return kDone_Verb;
1830 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001831 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001834 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836 fNeedClose = fForceClose;
1837 break;
1838 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001839 pts[0] = this->cons_moveTo();
1840 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 fLastPt = srcPts[0];
1842 fCloseLine = false;
1843 srcPts += 1;
1844 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001845 case kConic_Verb:
1846 fConicWeights += 1;
1847 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001849 pts[0] = this->cons_moveTo();
1850 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 fLastPt = srcPts[1];
1852 srcPts += 2;
1853 break;
1854 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001855 pts[0] = this->cons_moveTo();
1856 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857 fLastPt = srcPts[2];
1858 srcPts += 3;
1859 break;
1860 case kClose_Verb:
1861 verb = this->autoClose(pts);
1862 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001863 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864 } else {
1865 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001866 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869 break;
1870 }
1871 fPts = srcPts;
1872 return (Verb)verb;
1873}
1874
1875///////////////////////////////////////////////////////////////////////////////
1876
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001878 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879*/
1880
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001881size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 SkDEBUGCODE(this->validate();)
1883
halcanary96fcdcc2015-08-27 07:41:13 -07001884 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001885 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001886 return SkAlign4(byteCount);
1887 }
1888
1889 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001890
robertphillips@google.com466310d2013-12-03 16:43:54 +00001891 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001892 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001893 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001894 (fIsVolatile << kIsVolatile_SerializationShift) |
1895 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001896
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001897 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001898
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001899 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001900
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001901 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001902 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903}
1904
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001905size_t SkPath::readFromMemory(const void* storage, size_t length) {
1906 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001907
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001908 int32_t packed;
1909 if (!buffer.readS32(&packed)) {
1910 return 0;
1911 }
1912
reed8f086022015-06-11 14:22:19 -07001913 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001914
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001915 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1916 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001917 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001918 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001919 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08001920 if (!pathRef) {
1921 return 0;
1922 }
1923
1924 fPathRef.reset(pathRef);
1925 SkDEBUGCODE(this->validate();)
1926 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001927
reed8f086022015-06-11 14:22:19 -07001928 // compatibility check
1929 if (version < kPathPrivFirstDirection_Version) {
1930 switch (dir) { // old values
1931 case 0:
1932 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1933 break;
1934 case 1:
1935 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1936 break;
1937 case 2:
1938 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1939 break;
1940 default:
1941 SkASSERT(false);
1942 }
1943 } else {
1944 fFirstDirection = dir;
1945 }
1946
ajumaf8aec582016-01-13 13:46:31 -08001947 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948}
1949
1950///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951
reede05fed02014-12-15 07:59:53 -08001952#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001953#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001954
reed@google.com51bbe752013-01-17 22:07:50 +00001955static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001956 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001957 str->append(label);
1958 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001959
reed@google.com51bbe752013-01-17 22:07:50 +00001960 const SkScalar* values = &pts[0].fX;
1961 count *= 2;
1962
1963 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001964 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001965 if (i < count - 1) {
1966 str->append(", ");
1967 }
1968 }
reed@google.com277c3f82013-05-31 15:17:50 +00001969 if (conicWeight >= 0) {
1970 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001971 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001972 }
caryclark08fa28c2014-10-23 13:08:56 -07001973 str->append(");");
reede05fed02014-12-15 07:59:53 -08001974 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001975 str->append(" // ");
1976 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001977 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001978 if (i < count - 1) {
1979 str->append(", ");
1980 }
1981 }
1982 if (conicWeight >= 0) {
1983 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001984 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001985 }
1986 }
1987 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001988}
1989
caryclarke9562592014-09-15 09:26:09 -07001990void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001991 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 Iter iter(*this, forceClose);
1993 SkPoint pts[4];
1994 Verb verb;
1995
caryclark66a5d8b2014-06-24 08:30:15 -07001996 if (!wStream) {
1997 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
1998 }
reed@google.com51bbe752013-01-17 22:07:50 +00001999 SkString builder;
2000
reed@google.com4a3b7142012-05-16 17:16:46 +00002001 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 switch (verb) {
2003 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002004 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 break;
2006 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002007 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008 break;
2009 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002010 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002012 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002013 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002014 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002016 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017 break;
2018 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002019 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 break;
2021 default:
2022 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2023 verb = kDone_Verb; // stop the loop
2024 break;
2025 }
caryclark1049f122015-04-20 08:31:59 -07002026 if (!wStream && builder.size()) {
2027 SkDebugf("%s", builder.c_str());
2028 builder.reset();
2029 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030 }
caryclark66a5d8b2014-06-24 08:30:15 -07002031 if (wStream) {
2032 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002033 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034}
2035
reed@android.come522ca52009-11-23 20:10:41 +00002036void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002037 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002038}
2039
2040void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002041 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002042}
2043
2044#ifdef SK_DEBUG
2045void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002046 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002047
djsollen@google.com077348c2012-10-22 20:23:32 +00002048#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002049 if (!fBoundsIsDirty) {
2050 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002051
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002052 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002053 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002054
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002055 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002056 // if we're empty, fBounds may be empty but translated, so we can't
2057 // necessarily compare to bounds directly
2058 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2059 // be [2, 2, 2, 2]
2060 SkASSERT(bounds.isEmpty());
2061 SkASSERT(fBounds.isEmpty());
2062 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002063 if (bounds.isEmpty()) {
2064 SkASSERT(fBounds.isEmpty());
2065 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002066 if (!fBounds.isEmpty()) {
2067 SkASSERT(fBounds.contains(bounds));
2068 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002069 }
reed@android.come522ca52009-11-23 20:10:41 +00002070 }
2071 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002072#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002073}
djsollen@google.com077348c2012-10-22 20:23:32 +00002074#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002075
reed@google.com04863fa2011-05-15 04:08:24 +00002076///////////////////////////////////////////////////////////////////////////////
2077
reed@google.com0b7b9822011-05-16 12:29:27 +00002078static int sign(SkScalar x) { return x < 0; }
2079#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002080
robertphillipsc506e302014-09-16 09:43:31 -07002081enum DirChange {
2082 kLeft_DirChange,
2083 kRight_DirChange,
2084 kStraight_DirChange,
2085 kBackwards_DirChange,
2086
2087 kInvalid_DirChange
2088};
2089
2090
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002091static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002092 // The error epsilon was empirically derived; worse case round rects
2093 // with a mid point outset by 2x float epsilon in tests had an error
2094 // of 12.
2095 const int epsilon = 16;
2096 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2097 return false;
2098 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002099 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002100 int aBits = SkFloatAs2sCompliment(compA);
2101 int bBits = SkFloatAs2sCompliment(compB);
2102 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002103}
2104
caryclarkb4216502015-03-02 13:02:34 -08002105static bool approximately_zero_when_compared_to(double x, double y) {
2106 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002107}
2108
caryclarkb4216502015-03-02 13:02:34 -08002109
reed@google.com04863fa2011-05-15 04:08:24 +00002110// only valid for a single contour
2111struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002112 Convexicator()
2113 : fPtCount(0)
2114 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002115 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002116 , fIsFinite(true)
2117 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002118 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002119 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002120 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002121 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002122 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002123 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002124
2125 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002126 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002127 }
2128
2129 SkPath::Convexity getConvexity() const { return fConvexity; }
2130
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002131 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002132 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002133
reed@google.com04863fa2011-05-15 04:08:24 +00002134 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002135 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002136 return;
2137 }
2138
2139 if (0 == fPtCount) {
2140 fCurrPt = pt;
2141 ++fPtCount;
2142 } else {
2143 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002144 SkScalar lengthSqd = vec.lengthSqd();
2145 if (!SkScalarIsFinite(lengthSqd)) {
2146 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002147 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002148 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002149 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002150 fCurrPt = pt;
2151 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002152 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002153 } else {
2154 SkASSERT(fPtCount > 2);
2155 this->addVec(vec);
2156 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002157
reed@google.com85b6e392011-05-15 20:25:17 +00002158 int sx = sign(vec.fX);
2159 int sy = sign(vec.fY);
2160 fDx += (sx != fSx);
2161 fDy += (sy != fSy);
2162 fSx = sx;
2163 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002164
reed@google.com85b6e392011-05-15 20:25:17 +00002165 if (fDx > 3 || fDy > 3) {
2166 fConvexity = SkPath::kConcave_Convexity;
2167 }
reed@google.com04863fa2011-05-15 04:08:24 +00002168 }
2169 }
2170 }
2171
2172 void close() {
2173 if (fPtCount > 2) {
2174 this->addVec(fFirstVec);
2175 }
2176 }
2177
caryclarkb4216502015-03-02 13:02:34 -08002178 DirChange directionChange(const SkVector& curVec) {
2179 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2180
2181 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2182 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2183 largest = SkTMax(largest, -smallest);
2184
2185 if (!almost_equal(largest, largest + cross)) {
2186 int sign = SkScalarSignAsInt(cross);
2187 if (sign) {
2188 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2189 }
2190 }
2191
2192 if (cross) {
2193 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2194 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2195 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2196 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2197 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2198 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2199 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2200 if (sign) {
2201 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2202 }
2203 }
2204 }
2205
2206 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2207 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2208 fLastVec.dot(curVec) < 0.0f) {
2209 return kBackwards_DirChange;
2210 }
2211
2212 return kStraight_DirChange;
2213 }
2214
2215
caryclarkd3d1a982014-12-08 04:57:38 -08002216 bool isFinite() const {
2217 return fIsFinite;
2218 }
2219
caryclark5ccef572015-03-02 10:07:56 -08002220 void setCurve(bool isCurve) {
2221 fIsCurve = isCurve;
2222 }
2223
reed@google.com04863fa2011-05-15 04:08:24 +00002224private:
2225 void addVec(const SkVector& vec) {
2226 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002227 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002228 switch (dir) {
2229 case kLeft_DirChange: // fall through
2230 case kRight_DirChange:
2231 if (kInvalid_DirChange == fExpectedDir) {
2232 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002233 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2234 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002235 } else if (dir != fExpectedDir) {
2236 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002237 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002238 }
2239 fLastVec = vec;
2240 break;
2241 case kStraight_DirChange:
2242 break;
2243 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002244 if (fIsCurve) {
2245 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002246 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002247 }
robertphillipsc506e302014-09-16 09:43:31 -07002248 fLastVec = vec;
2249 break;
2250 case kInvalid_DirChange:
2251 SkFAIL("Use of invalid direction change flag");
2252 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002253 }
2254 }
2255
caryclarkb4216502015-03-02 13:02:34 -08002256 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002257 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002258 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002259 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2260 // value with the current vec is deemed to be of a significant value.
2261 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002262 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002263 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002264 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002265 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002266 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002267 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002268 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002269};
2270
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002271SkPath::Convexity SkPath::internalGetConvexity() const {
2272 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002273 SkPoint pts[4];
2274 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002275 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002276
2277 int contourCount = 0;
2278 int count;
2279 Convexicator state;
2280
caryclarkd3d1a982014-12-08 04:57:38 -08002281 if (!isFinite()) {
2282 return kUnknown_Convexity;
2283 }
caryclarke8c56662015-07-14 11:19:26 -07002284 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002285 switch (verb) {
2286 case kMove_Verb:
2287 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002288 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002289 return kConcave_Convexity;
2290 }
2291 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002292 // fall through
2293 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002294 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002295 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002296 break;
caryclark5ccef572015-03-02 10:07:56 -08002297 case kQuad_Verb:
2298 // fall through
2299 case kConic_Verb:
2300 // fall through
2301 case kCubic_Verb:
2302 count = 2 + (kCubic_Verb == verb);
2303 // As an additional enhancement, this could set curve true only
2304 // if the curve is nonlinear
2305 state.setCurve(true);
2306 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002307 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002308 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002309 state.close();
2310 count = 0;
2311 break;
2312 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002313 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002314 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002315 return kConcave_Convexity;
2316 }
2317
2318 for (int i = 1; i <= count; i++) {
2319 state.addPt(pts[i]);
2320 }
2321 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002322 if (!state.isFinite()) {
2323 return kUnknown_Convexity;
2324 }
reed@google.com04863fa2011-05-15 04:08:24 +00002325 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002326 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002327 return kConcave_Convexity;
2328 }
2329 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002330 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002331 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2332 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002333 }
2334 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002335}
reed@google.com69a99432012-01-10 18:00:10 +00002336
2337///////////////////////////////////////////////////////////////////////////////
2338
2339class ContourIter {
2340public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002341 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002342
2343 bool done() const { return fDone; }
2344 // if !done() then these may be called
2345 int count() const { return fCurrPtCount; }
2346 const SkPoint* pts() const { return fCurrPt; }
2347 void next();
2348
2349private:
2350 int fCurrPtCount;
2351 const SkPoint* fCurrPt;
2352 const uint8_t* fCurrVerb;
2353 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002354 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002355 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002356 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002357};
2358
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002359ContourIter::ContourIter(const SkPathRef& pathRef) {
2360 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002361 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002362 fCurrPt = pathRef.points();
2363 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002364 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002365 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002366 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002367 this->next();
2368}
2369
2370void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002371 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002372 fDone = true;
2373 }
2374 if (fDone) {
2375 return;
2376 }
2377
2378 // skip pts of prev contour
2379 fCurrPt += fCurrPtCount;
2380
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002381 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002382 int ptCount = 1; // moveTo
2383 const uint8_t* verbs = fCurrVerb;
2384
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002385 for (--verbs; verbs > fStopVerbs; --verbs) {
2386 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002387 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002388 goto CONTOUR_END;
2389 case SkPath::kLine_Verb:
2390 ptCount += 1;
2391 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002392 case SkPath::kConic_Verb:
2393 fCurrConicWeight += 1;
2394 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002395 case SkPath::kQuad_Verb:
2396 ptCount += 2;
2397 break;
2398 case SkPath::kCubic_Verb:
2399 ptCount += 3;
2400 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002401 case SkPath::kClose_Verb:
2402 break;
2403 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002404 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002405 break;
2406 }
2407 }
2408CONTOUR_END:
2409 fCurrPtCount = ptCount;
2410 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002411 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002412}
2413
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002414// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002415static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002416 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2417 // We may get 0 when the above subtracts underflow. We expect this to be
2418 // very rare and lazily promote to double.
2419 if (0 == cross) {
2420 double p0x = SkScalarToDouble(p0.fX);
2421 double p0y = SkScalarToDouble(p0.fY);
2422
2423 double p1x = SkScalarToDouble(p1.fX);
2424 double p1y = SkScalarToDouble(p1.fY);
2425
2426 double p2x = SkScalarToDouble(p2.fX);
2427 double p2y = SkScalarToDouble(p2.fY);
2428
2429 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2430 (p1y - p0y) * (p2x - p0x));
2431
2432 }
2433 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002434}
2435
reed@google.comc1ea60a2012-01-31 15:15:36 +00002436// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002437static int find_max_y(const SkPoint pts[], int count) {
2438 SkASSERT(count > 0);
2439 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002440 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002441 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002442 SkScalar y = pts[i].fY;
2443 if (y > max) {
2444 max = y;
2445 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002446 }
2447 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002448 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002449}
2450
reed@google.comcabaf1d2012-01-11 21:03:05 +00002451static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2452 int i = index;
2453 for (;;) {
2454 i = (i + inc) % n;
2455 if (i == index) { // we wrapped around, so abort
2456 break;
2457 }
2458 if (pts[index] != pts[i]) { // found a different point, success!
2459 break;
2460 }
2461 }
2462 return i;
2463}
2464
reed@google.comc1ea60a2012-01-31 15:15:36 +00002465/**
2466 * Starting at index, and moving forward (incrementing), find the xmin and
2467 * xmax of the contiguous points that have the same Y.
2468 */
2469static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2470 int* maxIndexPtr) {
2471 const SkScalar y = pts[index].fY;
2472 SkScalar min = pts[index].fX;
2473 SkScalar max = min;
2474 int minIndex = index;
2475 int maxIndex = index;
2476 for (int i = index + 1; i < n; ++i) {
2477 if (pts[i].fY != y) {
2478 break;
2479 }
2480 SkScalar x = pts[i].fX;
2481 if (x < min) {
2482 min = x;
2483 minIndex = i;
2484 } else if (x > max) {
2485 max = x;
2486 maxIndex = i;
2487 }
2488 }
2489 *maxIndexPtr = maxIndex;
2490 return minIndex;
2491}
2492
reed026beb52015-06-10 14:23:15 -07002493static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2494 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002495}
2496
reed@google.comac8543f2012-01-30 20:51:25 +00002497/*
2498 * We loop through all contours, and keep the computed cross-product of the
2499 * contour that contained the global y-max. If we just look at the first
2500 * contour, we may find one that is wound the opposite way (correctly) since
2501 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2502 * that is outer most (or at least has the global y-max) before we can consider
2503 * its cross product.
2504 */
reed026beb52015-06-10 14:23:15 -07002505bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002506 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2507 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002508 return true;
2509 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002510
2511 // don't want to pay the cost for computing this if it
2512 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002513 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2514 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002515 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002516 return false;
2517 }
reed@google.com69a99432012-01-10 18:00:10 +00002518
reed026beb52015-06-10 14:23:15 -07002519 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002520
reed@google.comac8543f2012-01-30 20:51:25 +00002521 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002522 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002523 SkScalar ymaxCross = 0;
2524
reed@google.com69a99432012-01-10 18:00:10 +00002525 for (; !iter.done(); iter.next()) {
2526 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002527 if (n < 3) {
2528 continue;
2529 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002530
reed@google.comcabaf1d2012-01-11 21:03:05 +00002531 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002532 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002533 int index = find_max_y(pts, n);
2534 if (pts[index].fY < ymax) {
2535 continue;
2536 }
2537
2538 // If there is more than 1 distinct point at the y-max, we take the
2539 // x-min and x-max of them and just subtract to compute the dir.
2540 if (pts[(index + 1) % n].fY == pts[index].fY) {
2541 int maxIndex;
2542 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2543 if (minIndex == maxIndex) {
2544 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002545 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002546 SkASSERT(pts[minIndex].fY == pts[index].fY);
2547 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2548 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2549 // we just subtract the indices, and let that auto-convert to
2550 // SkScalar, since we just want - or + to signal the direction.
2551 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002552 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002553 TRY_CROSSPROD:
2554 // Find a next and prev index to use for the cross-product test,
2555 // but we try to find pts that form non-zero vectors from pts[index]
2556 //
2557 // Its possible that we can't find two non-degenerate vectors, so
2558 // we have to guard our search (e.g. all the pts could be in the
2559 // same place).
2560
2561 // we pass n - 1 instead of -1 so we don't foul up % operator by
2562 // passing it a negative LH argument.
2563 int prev = find_diff_pt(pts, index, n, n - 1);
2564 if (prev == index) {
2565 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002566 continue;
2567 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002568 int next = find_diff_pt(pts, index, n, 1);
2569 SkASSERT(next != index);
2570 cross = cross_prod(pts[prev], pts[index], pts[next]);
2571 // if we get a zero and the points are horizontal, then we look at the spread in
2572 // x-direction. We really should continue to walk away from the degeneracy until
2573 // there is a divergence.
2574 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2575 // construct the subtract so we get the correct Direction below
2576 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002577 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002578 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002579
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002580 if (cross) {
2581 // record our best guess so far
2582 ymax = pts[index].fY;
2583 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002584 }
2585 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002586 if (ymaxCross) {
2587 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002588 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002589 return true;
2590 } else {
2591 return false;
2592 }
reed@google.comac8543f2012-01-30 20:51:25 +00002593}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002594
2595///////////////////////////////////////////////////////////////////////////////
2596
caryclark9aacd902015-12-14 08:38:09 -08002597static bool between(SkScalar a, SkScalar b, SkScalar c) {
2598 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2599 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2600 return (a - b) * (c - b) <= 0;
2601}
2602
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002603static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2604 SkScalar D, SkScalar t) {
2605 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2606}
2607
2608static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2609 SkScalar t) {
2610 SkScalar A = c3 + 3*(c1 - c2) - c0;
2611 SkScalar B = 3*(c2 - c1 - c1 + c0);
2612 SkScalar C = 3*(c1 - c0);
2613 SkScalar D = c0;
2614 return eval_cubic_coeff(A, B, C, D, t);
2615}
2616
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002617template <size_t N> static void find_minmax(const SkPoint pts[],
2618 SkScalar* minPtr, SkScalar* maxPtr) {
2619 SkScalar min, max;
2620 min = max = pts[0].fX;
2621 for (size_t i = 1; i < N; ++i) {
2622 min = SkMinScalar(min, pts[i].fX);
2623 max = SkMaxScalar(max, pts[i].fX);
2624 }
2625 *minPtr = min;
2626 *maxPtr = max;
2627}
2628
caryclark9cb5d752015-12-18 04:35:24 -08002629static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2630 if (start.fY == end.fY) {
2631 return between(start.fX, x, end.fX) && x != end.fX;
2632 } else {
2633 return x == start.fX && y == start.fY;
2634 }
2635}
2636
caryclark9aacd902015-12-14 08:38:09 -08002637static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002638 SkScalar y0 = pts[0].fY;
2639 SkScalar y3 = pts[3].fY;
2640
2641 int dir = 1;
2642 if (y0 > y3) {
2643 SkTSwap(y0, y3);
2644 dir = -1;
2645 }
2646 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002647 return 0;
2648 }
caryclark9cb5d752015-12-18 04:35:24 -08002649 if (checkOnCurve(x, y, pts[0], pts[3])) {
2650 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002651 return 0;
2652 }
caryclark9cb5d752015-12-18 04:35:24 -08002653 if (y == y3) {
2654 return 0;
2655 }
2656
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002657 // quickreject or quickaccept
2658 SkScalar min, max;
2659 find_minmax<4>(pts, &min, &max);
2660 if (x < min) {
2661 return 0;
2662 }
2663 if (x > max) {
2664 return dir;
2665 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002666
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002667 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002668 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002669 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002670 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002671 if (SkScalarNearlyEqual(xt, x)) {
2672 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2673 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002674 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002675 }
2676 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002677 return xt < x ? dir : 0;
2678}
2679
caryclark9aacd902015-12-14 08:38:09 -08002680static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002681 SkPoint dst[10];
2682 int n = SkChopCubicAtYExtrema(pts, dst);
2683 int w = 0;
2684 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002685 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002686 }
2687 return w;
2688}
2689
caryclark9aacd902015-12-14 08:38:09 -08002690static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2691 SkASSERT(src);
2692 SkASSERT(t >= 0 && t <= 1);
2693 SkScalar src2w = src[2] * w;
2694 SkScalar C = src[0];
2695 SkScalar A = src[4] - 2 * src2w + C;
2696 SkScalar B = 2 * (src2w - C);
2697 return (A * t + B) * t + C;
2698}
2699
2700
2701static double conic_eval_denominator(SkScalar w, SkScalar t) {
2702 SkScalar B = 2 * (w - 1);
2703 SkScalar C = 1;
2704 SkScalar A = -B;
2705 return (A * t + B) * t + C;
2706}
2707
2708static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2709 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002710 SkScalar y0 = pts[0].fY;
2711 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002712
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002713 int dir = 1;
2714 if (y0 > y2) {
2715 SkTSwap(y0, y2);
2716 dir = -1;
2717 }
caryclark9aacd902015-12-14 08:38:09 -08002718 if (y < y0 || y > y2) {
2719 return 0;
2720 }
caryclark9cb5d752015-12-18 04:35:24 -08002721 if (checkOnCurve(x, y, pts[0], pts[2])) {
2722 *onCurveCount += 1;
2723 return 0;
2724 }
caryclark9aacd902015-12-14 08:38:09 -08002725 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002726 return 0;
2727 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002728
caryclark9aacd902015-12-14 08:38:09 -08002729 SkScalar roots[2];
2730 SkScalar A = pts[2].fY;
2731 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2732 SkScalar C = pts[0].fY;
2733 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2734 B -= C; // B = b*w - w * yCept + yCept - a
2735 C -= y;
2736 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2737 SkASSERT(n <= 1);
2738 SkScalar xt;
2739 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002740 // zero roots are returned only when y0 == y
2741 // Need [0] if dir == 1
2742 // and [2] if dir == -1
2743 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002744 } else {
2745 SkScalar t = roots[0];
2746 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2747 }
2748 if (SkScalarNearlyEqual(xt, x)) {
2749 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2750 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002751 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002752 }
2753 }
2754 return xt < x ? dir : 0;
2755}
2756
2757static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2758 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2759 if (y0 == y1) {
2760 return true;
2761 }
2762 if (y0 < y1) {
2763 return y1 <= y2;
2764 } else {
2765 return y1 >= y2;
2766 }
2767}
2768
2769static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2770 int* onCurveCount) {
2771 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002772 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002773 // If the data points are very large, the conic may not be monotonic but may also
2774 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002775 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2776 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2777 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002778 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2779 }
2780 return w;
2781}
2782
2783static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2784 SkScalar y0 = pts[0].fY;
2785 SkScalar y2 = pts[2].fY;
2786
2787 int dir = 1;
2788 if (y0 > y2) {
2789 SkTSwap(y0, y2);
2790 dir = -1;
2791 }
2792 if (y < y0 || y > y2) {
2793 return 0;
2794 }
caryclark9cb5d752015-12-18 04:35:24 -08002795 if (checkOnCurve(x, y, pts[0], pts[2])) {
2796 *onCurveCount += 1;
2797 return 0;
2798 }
caryclark9aacd902015-12-14 08:38:09 -08002799 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002800 return 0;
2801 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002802 // bounds check on X (not required. is it faster?)
2803#if 0
2804 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2805 return 0;
2806 }
2807#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 SkScalar roots[2];
2810 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2811 2 * (pts[1].fY - pts[0].fY),
2812 pts[0].fY - y,
2813 roots);
2814 SkASSERT(n <= 1);
2815 SkScalar xt;
2816 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002817 // zero roots are returned only when y0 == y
2818 // Need [0] if dir == 1
2819 // and [2] if dir == -1
2820 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002821 } else {
2822 SkScalar t = roots[0];
2823 SkScalar C = pts[0].fX;
2824 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2825 SkScalar B = 2 * (pts[1].fX - C);
2826 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2827 }
caryclark9aacd902015-12-14 08:38:09 -08002828 if (SkScalarNearlyEqual(xt, x)) {
2829 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2830 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002831 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002832 }
2833 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002834 return xt < x ? dir : 0;
2835}
2836
caryclark9aacd902015-12-14 08:38:09 -08002837static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 SkPoint dst[5];
2839 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2842 n = SkChopQuadAtYExtrema(pts, dst);
2843 pts = dst;
2844 }
caryclark9aacd902015-12-14 08:38:09 -08002845 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002847 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002848 }
2849 return w;
2850}
2851
caryclark9aacd902015-12-14 08:38:09 -08002852static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 SkScalar x0 = pts[0].fX;
2854 SkScalar y0 = pts[0].fY;
2855 SkScalar x1 = pts[1].fX;
2856 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002857
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002858 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002859
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 int dir = 1;
2861 if (y0 > y1) {
2862 SkTSwap(y0, y1);
2863 dir = -1;
2864 }
caryclark9aacd902015-12-14 08:38:09 -08002865 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002866 return 0;
2867 }
caryclark9cb5d752015-12-18 04:35:24 -08002868 if (checkOnCurve(x, y, pts[0], pts[1])) {
2869 *onCurveCount += 1;
2870 return 0;
2871 }
2872 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002873 return 0;
2874 }
2875 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002876
caryclark9aacd902015-12-14 08:38:09 -08002877 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002878 // zero cross means the point is on the line, and since the case where
2879 // y of the query point is at the end point is handled above, we can be
2880 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002881 if (x != x1 || y != pts[1].fY) {
2882 *onCurveCount += 1;
2883 }
caryclark9aacd902015-12-14 08:38:09 -08002884 dir = 0;
2885 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 dir = 0;
2887 }
2888 return dir;
2889}
2890
caryclark9aacd902015-12-14 08:38:09 -08002891static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
2892 SkTDArray<SkVector>* tangents) {
2893 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
2894 && !between(pts[2].fY, y, pts[3].fY)) {
2895 return;
2896 }
2897 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
2898 && !between(pts[2].fX, x, pts[3].fX)) {
2899 return;
2900 }
2901 SkPoint dst[10];
2902 int n = SkChopCubicAtYExtrema(pts, dst);
2903 for (int i = 0; i <= n; ++i) {
2904 SkPoint* c = &dst[i * 3];
2905 SkScalar t;
2906 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
2907 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
2908 if (!SkScalarNearlyEqual(x, xt)) {
2909 continue;
2910 }
2911 SkVector tangent;
2912 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
2913 tangents->push(tangent);
2914 }
2915}
2916
2917static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
2918 SkTDArray<SkVector>* tangents) {
2919 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
2920 return;
2921 }
2922 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
2923 return;
2924 }
2925 SkScalar roots[2];
2926 SkScalar A = pts[2].fY;
2927 SkScalar B = pts[1].fY * w - y * w + y;
2928 SkScalar C = pts[0].fY;
2929 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2930 B -= C; // B = b*w - w * yCept + yCept - a
2931 C -= y;
2932 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2933 for (int index = 0; index < n; ++index) {
2934 SkScalar t = roots[index];
2935 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
2936 if (!SkScalarNearlyEqual(x, xt)) {
2937 continue;
2938 }
2939 SkConic conic(pts, w);
2940 tangents->push(conic.evalTangentAt(t));
2941 }
2942}
2943
2944static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
2945 SkTDArray<SkVector>* tangents) {
2946 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
2947 return;
2948 }
2949 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
2950 return;
2951 }
2952 SkScalar roots[2];
2953 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2954 2 * (pts[1].fY - pts[0].fY),
2955 pts[0].fY - y,
2956 roots);
2957 for (int index = 0; index < n; ++index) {
2958 SkScalar t = roots[index];
2959 SkScalar C = pts[0].fX;
2960 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2961 SkScalar B = 2 * (pts[1].fX - C);
2962 SkScalar xt = (A * t + B) * t + C;
2963 if (!SkScalarNearlyEqual(x, xt)) {
2964 continue;
2965 }
2966 tangents->push(SkEvalQuadTangentAt(pts, t));
2967 }
2968}
2969
2970static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
2971 SkTDArray<SkVector>* tangents) {
2972 SkScalar y0 = pts[0].fY;
2973 SkScalar y1 = pts[1].fY;
2974 if (!between(y0, y, y1)) {
2975 return;
2976 }
2977 SkScalar x0 = pts[0].fX;
2978 SkScalar x1 = pts[1].fX;
2979 if (!between(x0, x, x1)) {
2980 return;
2981 }
2982 SkScalar dx = x1 - x0;
2983 SkScalar dy = y1 - y0;
2984 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
2985 return;
2986 }
2987 SkVector v;
2988 v.set(dx, dy);
2989 tangents->push(v);
2990}
2991
reed@google.com4db592c2013-10-30 17:39:43 +00002992static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2993 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2994}
2995
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996bool SkPath::contains(SkScalar x, SkScalar y) const {
2997 bool isInverse = this->isInverseFillType();
2998 if (this->isEmpty()) {
2999 return isInverse;
3000 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003001
reed@google.com4db592c2013-10-30 17:39:43 +00003002 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 return isInverse;
3004 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003005
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 SkPath::Iter iter(*this, true);
3007 bool done = false;
3008 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003009 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 do {
3011 SkPoint pts[4];
3012 switch (iter.next(pts, false)) {
3013 case SkPath::kMove_Verb:
3014 case SkPath::kClose_Verb:
3015 break;
3016 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003017 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003018 break;
3019 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003020 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003021 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003022 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003023 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003024 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003026 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003027 break;
3028 case SkPath::kDone_Verb:
3029 done = true;
3030 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003031 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003032 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003033 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3034 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3035 if (evenOddFill) {
3036 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003037 }
caryclark9aacd902015-12-14 08:38:09 -08003038 if (w) {
3039 return !isInverse;
3040 }
3041 if (onCurveCount <= 1) {
3042 return SkToBool(onCurveCount) ^ isInverse;
3043 }
3044 if ((onCurveCount & 1) || evenOddFill) {
3045 return SkToBool(onCurveCount & 1) ^ isInverse;
3046 }
3047 // If the point touches an even number of curves, and the fill is winding, check for
3048 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3049 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003050 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003051 SkTDArray<SkVector> tangents;
3052 do {
3053 SkPoint pts[4];
3054 int oldCount = tangents.count();
3055 switch (iter.next(pts, false)) {
3056 case SkPath::kMove_Verb:
3057 case SkPath::kClose_Verb:
3058 break;
3059 case SkPath::kLine_Verb:
3060 tangent_line(pts, x, y, &tangents);
3061 break;
3062 case SkPath::kQuad_Verb:
3063 tangent_quad(pts, x, y, &tangents);
3064 break;
3065 case SkPath::kConic_Verb:
3066 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3067 break;
3068 case SkPath::kCubic_Verb:
3069 tangent_cubic(pts, x, y, &tangents);
3070 break;
3071 case SkPath::kDone_Verb:
3072 done = true;
3073 break;
3074 }
3075 if (tangents.count() > oldCount) {
3076 int last = tangents.count() - 1;
3077 const SkVector& tangent = tangents[last];
3078 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3079 tangents.remove(last);
3080 } else {
3081 for (int index = 0; index < last; ++index) {
3082 const SkVector& test = tangents[index];
3083 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003084 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3085 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003086 tangents.remove(last);
3087 tangents.removeShuffle(index);
3088 break;
3089 }
3090 }
3091 }
3092 }
3093 } while (!done);
3094 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003095}
fmalitaaa0df4e2015-12-01 09:13:23 -08003096
3097int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3098 SkScalar w, SkPoint pts[], int pow2) {
3099 const SkConic conic(p0, p1, p2, w);
3100 return conic.chopIntoQuadsPOW2(pts, pow2);
3101}