blob: c77a25b4aa6dca74c6ac2988ba6c85ddc2f50216 [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
caryclark88651ae2016-01-20 11:55:11 -08001304 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305
1306 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1307 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001308#ifndef SK_SUPPORT_LEGACY_ARCTO
1309 after.setLength(dist);
1310 this->lineTo(xx, yy);
1311 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1312 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
1313#else
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 SkRotationDirection arcDir;
1315
1316 // now turn before/after into normals
1317 if (sinh > 0) {
1318 before.rotateCCW();
1319 after.rotateCCW();
1320 arcDir = kCW_SkRotationDirection;
1321 } else {
1322 before.rotateCW();
1323 after.rotateCW();
1324 arcDir = kCCW_SkRotationDirection;
1325 }
1326
1327 SkMatrix matrix;
1328 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001329
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 matrix.setScale(radius, radius);
1331 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1332 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001333
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001335
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 this->incReserve(count);
1337 // [xx,yy] == pts[0]
1338 this->lineTo(xx, yy);
1339 for (int i = 1; i < count; i += 2) {
1340 this->quadTo(pts[i], pts[i+1]);
1341 }
caryclark88651ae2016-01-20 11:55:11 -08001342#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343}
1344
1345///////////////////////////////////////////////////////////////////////////////
1346
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001347void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001348 SkMatrix matrix;
1349
1350 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001351 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352}
1353
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001354void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001355 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001357 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358 SkPoint pts[4];
1359 Verb verb;
1360
1361 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001362 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001363 while ((verb = iter.next(pts)) != kDone_Verb) {
1364 switch (verb) {
1365 case kMove_Verb:
1366 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001367 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1368 injectMoveToIfNeeded(); // In case last contour is closed
1369 this->lineTo(pts[0]);
1370 } else {
1371 this->moveTo(pts[0]);
1372 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 break;
1374 case kLine_Verb:
1375 proc(matrix, &pts[1], &pts[1], 1);
1376 this->lineTo(pts[1]);
1377 break;
1378 case kQuad_Verb:
1379 proc(matrix, &pts[1], &pts[1], 2);
1380 this->quadTo(pts[1], pts[2]);
1381 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001382 case kConic_Verb:
1383 proc(matrix, &pts[1], &pts[1], 2);
1384 this->conicTo(pts[1], pts[2], iter.conicWeight());
1385 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 case kCubic_Verb:
1387 proc(matrix, &pts[1], &pts[1], 3);
1388 this->cubicTo(pts[1], pts[2], pts[3]);
1389 break;
1390 case kClose_Verb:
1391 this->close();
1392 break;
1393 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001394 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001396 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001397 }
1398}
1399
1400///////////////////////////////////////////////////////////////////////////////
1401
reed@google.com277c3f82013-05-31 15:17:50 +00001402static int pts_in_verb(unsigned verb) {
1403 static const uint8_t gPtsInVerb[] = {
1404 1, // kMove
1405 1, // kLine
1406 2, // kQuad
1407 2, // kConic
1408 3, // kCubic
1409 0, // kClose
1410 0 // kDone
1411 };
1412
1413 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1414 return gPtsInVerb[verb];
1415}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416
reed@android.com8a1c16f2008-12-17 15:59:43 +00001417// ignore the last point of the 1st contour
1418void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001419 int i, vcount = path.fPathRef->countVerbs();
1420 // exit early if the path is empty, or just has a moveTo.
1421 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 return;
1423 }
1424
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001425 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001427 const uint8_t* verbs = path.fPathRef->verbs();
1428 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001429 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001431 SkASSERT(verbs[~0] == kMove_Verb);
1432 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001433 unsigned v = verbs[~i];
1434 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435 if (n == 0) {
1436 break;
1437 }
1438 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001439 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 }
1441
1442 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001443 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444 case kLine_Verb:
1445 this->lineTo(pts[-1].fX, pts[-1].fY);
1446 break;
1447 case kQuad_Verb:
1448 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1449 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001450 case kConic_Verb:
1451 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1452 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 case kCubic_Verb:
1454 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1455 pts[-3].fX, pts[-3].fY);
1456 break;
1457 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001458 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459 break;
1460 }
reed@google.com277c3f82013-05-31 15:17:50 +00001461 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 }
1463}
1464
reed@google.com63d73742012-01-10 15:33:12 +00001465void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001466 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 const SkPoint* pts = src.fPathRef->pointsEnd();
1469 // we will iterator through src's verbs backwards
1470 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1471 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001472 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001473
1474 bool needMove = true;
1475 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001476 while (verbs < verbsEnd) {
1477 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001478 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001479
1480 if (needMove) {
1481 --pts;
1482 this->moveTo(pts->fX, pts->fY);
1483 needMove = false;
1484 }
1485 pts -= n;
1486 switch (v) {
1487 case kMove_Verb:
1488 if (needClose) {
1489 this->close();
1490 needClose = false;
1491 }
1492 needMove = true;
1493 pts += 1; // so we see the point in "if (needMove)" above
1494 break;
1495 case kLine_Verb:
1496 this->lineTo(pts[0]);
1497 break;
1498 case kQuad_Verb:
1499 this->quadTo(pts[1], pts[0]);
1500 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001501 case kConic_Verb:
1502 this->conicTo(pts[1], pts[0], *--conicWeights);
1503 break;
reed@google.com63d73742012-01-10 15:33:12 +00001504 case kCubic_Verb:
1505 this->cubicTo(pts[2], pts[1], pts[0]);
1506 break;
1507 case kClose_Verb:
1508 needClose = true;
1509 break;
1510 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001511 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001512 }
1513 }
1514}
1515
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516///////////////////////////////////////////////////////////////////////////////
1517
1518void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1519 SkMatrix matrix;
1520
1521 matrix.setTranslate(dx, dy);
1522 this->transform(matrix, dst);
1523}
1524
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1526 int level = 2) {
1527 if (--level >= 0) {
1528 SkPoint tmp[7];
1529
1530 SkChopCubicAtHalf(pts, tmp);
1531 subdivide_cubic_to(path, &tmp[0], level);
1532 subdivide_cubic_to(path, &tmp[3], level);
1533 } else {
1534 path->cubicTo(pts[1], pts[2], pts[3]);
1535 }
1536}
1537
1538void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1539 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001540 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 dst = (SkPath*)this;
1542 }
1543
tomhudson@google.com8d430182011-06-06 19:11:19 +00001544 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 SkPath tmp;
1546 tmp.fFillType = fFillType;
1547
1548 SkPath::Iter iter(*this, false);
1549 SkPoint pts[4];
1550 SkPath::Verb verb;
1551
reed@google.com4a3b7142012-05-16 17:16:46 +00001552 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 switch (verb) {
1554 case kMove_Verb:
1555 tmp.moveTo(pts[0]);
1556 break;
1557 case kLine_Verb:
1558 tmp.lineTo(pts[1]);
1559 break;
1560 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001561 // promote the quad to a conic
1562 tmp.conicTo(pts[1], pts[2],
1563 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001565 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001566 tmp.conicTo(pts[1], pts[2],
1567 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001568 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 case kCubic_Verb:
1570 subdivide_cubic_to(&tmp, pts);
1571 break;
1572 case kClose_Verb:
1573 tmp.close();
1574 break;
1575 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001576 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 break;
1578 }
1579 }
1580
1581 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001582 SkPathRef::Editor ed(&dst->fPathRef);
1583 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001584 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001586 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1587
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001590 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001591 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001593
reed026beb52015-06-10 14:23:15 -07001594 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1595 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001596 } else {
1597 SkScalar det2x2 =
1598 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1599 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1600 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001601 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1602 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001603 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001604 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001605 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001606 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001607 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001608 }
1609 }
1610
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 SkDEBUGCODE(dst->validate();)
1612 }
1613}
1614
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615///////////////////////////////////////////////////////////////////////////////
1616///////////////////////////////////////////////////////////////////////////////
1617
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001618enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001619 kEmptyContour_SegmentState, // The current contour is empty. We may be
1620 // starting processing or we may have just
1621 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001622 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1623 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1624 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625};
1626
1627SkPath::Iter::Iter() {
1628#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001629 fPts = nullptr;
1630 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001632 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001633 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634#endif
1635 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001636 fVerbs = nullptr;
1637 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 fNeedClose = false;
1639}
1640
1641SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1642 this->setPath(path, forceClose);
1643}
1644
1645void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001646 fPts = path.fPathRef->points();
1647 fVerbs = path.fPathRef->verbs();
1648 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001649 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001650 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001651 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 fForceClose = SkToU8(forceClose);
1653 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001654 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655}
1656
1657bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001658 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 return false;
1660 }
1661 if (fForceClose) {
1662 return true;
1663 }
1664
1665 const uint8_t* verbs = fVerbs;
1666 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001667
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 if (kMove_Verb == *(verbs - 1)) {
1669 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 }
1671
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 while (verbs > stop) {
1673 // verbs points one beyond the current verb, decrement first.
1674 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 if (kMove_Verb == v) {
1676 break;
1677 }
1678 if (kClose_Verb == v) {
1679 return true;
1680 }
1681 }
1682 return false;
1683}
1684
1685SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001686 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001688 // A special case: if both points are NaN, SkPoint::operation== returns
1689 // false, but the iterator expects that they are treated as the same.
1690 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001691 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1692 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001693 return kClose_Verb;
1694 }
1695
reed@google.com9e25dbf2012-05-15 17:05:38 +00001696 pts[0] = fLastPt;
1697 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 fLastPt = fMoveTo;
1699 fCloseLine = true;
1700 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001701 } else {
1702 pts[0] = fMoveTo;
1703 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705}
1706
reed@google.com9e25dbf2012-05-15 17:05:38 +00001707const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708 if (fSegmentState == kAfterMove_SegmentState) {
1709 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001710 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001711 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001713 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1714 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001715 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717}
1718
caryclarke8c56662015-07-14 11:19:26 -07001719void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001720 // We need to step over anything that will not move the current draw point
1721 // forward before the next move is seen
1722 const uint8_t* lastMoveVerb = 0;
1723 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001724 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725 SkPoint lastPt = fLastPt;
1726 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001727 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728 switch (verb) {
1729 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 // Keep a record of this most recent move
1731 lastMoveVerb = fVerbs;
1732 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001733 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001734 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001735 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 fPts++;
1737 break;
1738
1739 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001740 // A close when we are in a segment is always valid except when it
1741 // follows a move which follows a segment.
1742 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743 return;
1744 }
1745 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001746 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001747 break;
1748
1749 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001750 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001751 if (lastMoveVerb) {
1752 fVerbs = lastMoveVerb;
1753 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001754 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001755 return;
1756 }
1757 return;
1758 }
1759 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001761 fPts++;
1762 break;
1763
reed@google.com277c3f82013-05-31 15:17:50 +00001764 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001765 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001766 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001767 if (lastMoveVerb) {
1768 fVerbs = lastMoveVerb;
1769 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001770 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001771 return;
1772 }
1773 return;
1774 }
1775 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001776 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001777 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001778 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001779 break;
1780
1781 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001782 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 if (lastMoveVerb) {
1784 fVerbs = lastMoveVerb;
1785 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001786 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001787 return;
1788 }
1789 return;
1790 }
1791 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001793 fPts += 3;
1794 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001795
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001797 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001798 }
1799 }
1800}
1801
reed@google.com4a3b7142012-05-16 17:16:46 +00001802SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001803 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 // Close the curve if requested and if there is some curve to close
1807 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001808 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 return kLine_Verb;
1810 }
1811 fNeedClose = false;
1812 return kClose_Verb;
1813 }
1814 return kDone_Verb;
1815 }
1816
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001817 // fVerbs is one beyond the current verb, decrement first
1818 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001819 const SkPoint* SK_RESTRICT srcPts = fPts;
1820 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821
1822 switch (verb) {
1823 case kMove_Verb:
1824 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001825 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 verb = this->autoClose(pts);
1827 if (verb == kClose_Verb) {
1828 fNeedClose = false;
1829 }
1830 return (Verb)verb;
1831 }
1832 if (fVerbs == fVerbStop) { // might be a trailing moveto
1833 return kDone_Verb;
1834 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001835 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001836 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001838 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 fNeedClose = fForceClose;
1841 break;
1842 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001843 pts[0] = this->cons_moveTo();
1844 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845 fLastPt = srcPts[0];
1846 fCloseLine = false;
1847 srcPts += 1;
1848 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001849 case kConic_Verb:
1850 fConicWeights += 1;
1851 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001853 pts[0] = this->cons_moveTo();
1854 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 fLastPt = srcPts[1];
1856 srcPts += 2;
1857 break;
1858 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001859 pts[0] = this->cons_moveTo();
1860 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 fLastPt = srcPts[2];
1862 srcPts += 3;
1863 break;
1864 case kClose_Verb:
1865 verb = this->autoClose(pts);
1866 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 } else {
1869 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001870 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001873 break;
1874 }
1875 fPts = srcPts;
1876 return (Verb)verb;
1877}
1878
1879///////////////////////////////////////////////////////////////////////////////
1880
reed@android.com8a1c16f2008-12-17 15:59:43 +00001881/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001882 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883*/
1884
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001885size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886 SkDEBUGCODE(this->validate();)
1887
halcanary96fcdcc2015-08-27 07:41:13 -07001888 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001889 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001890 return SkAlign4(byteCount);
1891 }
1892
1893 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001894
robertphillips@google.com466310d2013-12-03 16:43:54 +00001895 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001896 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001897 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001898 (fIsVolatile << kIsVolatile_SerializationShift) |
1899 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001900
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001901 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001902
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001903 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001904
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001905 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001906 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907}
1908
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001909size_t SkPath::readFromMemory(const void* storage, size_t length) {
1910 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001911
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001912 int32_t packed;
1913 if (!buffer.readS32(&packed)) {
1914 return 0;
1915 }
1916
reed8f086022015-06-11 14:22:19 -07001917 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001918
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001919 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1920 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001921 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07001922 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00001923 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08001924 if (!pathRef) {
1925 return 0;
1926 }
1927
1928 fPathRef.reset(pathRef);
1929 SkDEBUGCODE(this->validate();)
1930 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00001931
reed8f086022015-06-11 14:22:19 -07001932 // compatibility check
1933 if (version < kPathPrivFirstDirection_Version) {
1934 switch (dir) { // old values
1935 case 0:
1936 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
1937 break;
1938 case 1:
1939 fFirstDirection = SkPathPriv::kCW_FirstDirection;
1940 break;
1941 case 2:
1942 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
1943 break;
1944 default:
1945 SkASSERT(false);
1946 }
1947 } else {
1948 fFirstDirection = dir;
1949 }
1950
ajumaf8aec582016-01-13 13:46:31 -08001951 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952}
1953
1954///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00001955
reede05fed02014-12-15 07:59:53 -08001956#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07001957#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00001958
reed@google.com51bbe752013-01-17 22:07:50 +00001959static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08001960 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00001961 str->append(label);
1962 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00001963
reed@google.com51bbe752013-01-17 22:07:50 +00001964 const SkScalar* values = &pts[0].fX;
1965 count *= 2;
1966
1967 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001968 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00001969 if (i < count - 1) {
1970 str->append(", ");
1971 }
1972 }
reed@google.com277c3f82013-05-31 15:17:50 +00001973 if (conicWeight >= 0) {
1974 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001975 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00001976 }
caryclark08fa28c2014-10-23 13:08:56 -07001977 str->append(");");
reede05fed02014-12-15 07:59:53 -08001978 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07001979 str->append(" // ");
1980 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08001981 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07001982 if (i < count - 1) {
1983 str->append(", ");
1984 }
1985 }
1986 if (conicWeight >= 0) {
1987 str->append(", ");
reede05fed02014-12-15 07:59:53 -08001988 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07001989 }
1990 }
1991 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00001992}
1993
caryclarke9562592014-09-15 09:26:09 -07001994void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08001995 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 Iter iter(*this, forceClose);
1997 SkPoint pts[4];
1998 Verb verb;
1999
caryclark66a5d8b2014-06-24 08:30:15 -07002000 if (!wStream) {
2001 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2002 }
reed@google.com51bbe752013-01-17 22:07:50 +00002003 SkString builder;
2004
reed@google.com4a3b7142012-05-16 17:16:46 +00002005 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002006 switch (verb) {
2007 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002008 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 break;
2010 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002011 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002012 break;
2013 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002014 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002016 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002017 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002018 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002020 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021 break;
2022 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002023 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 break;
2025 default:
2026 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2027 verb = kDone_Verb; // stop the loop
2028 break;
2029 }
caryclark1049f122015-04-20 08:31:59 -07002030 if (!wStream && builder.size()) {
2031 SkDebugf("%s", builder.c_str());
2032 builder.reset();
2033 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 }
caryclark66a5d8b2014-06-24 08:30:15 -07002035 if (wStream) {
2036 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002037 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002038}
2039
reed@android.come522ca52009-11-23 20:10:41 +00002040void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002041 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002042}
2043
2044void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002045 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002046}
2047
2048#ifdef SK_DEBUG
2049void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002050 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002051
djsollen@google.com077348c2012-10-22 20:23:32 +00002052#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002053 if (!fBoundsIsDirty) {
2054 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002055
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002056 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002057 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002058
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002059 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002060 // if we're empty, fBounds may be empty but translated, so we can't
2061 // necessarily compare to bounds directly
2062 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2063 // be [2, 2, 2, 2]
2064 SkASSERT(bounds.isEmpty());
2065 SkASSERT(fBounds.isEmpty());
2066 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002067 if (bounds.isEmpty()) {
2068 SkASSERT(fBounds.isEmpty());
2069 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002070 if (!fBounds.isEmpty()) {
2071 SkASSERT(fBounds.contains(bounds));
2072 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002073 }
reed@android.come522ca52009-11-23 20:10:41 +00002074 }
2075 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002076#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002077}
djsollen@google.com077348c2012-10-22 20:23:32 +00002078#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002079
reed@google.com04863fa2011-05-15 04:08:24 +00002080///////////////////////////////////////////////////////////////////////////////
2081
reed@google.com0b7b9822011-05-16 12:29:27 +00002082static int sign(SkScalar x) { return x < 0; }
2083#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002084
robertphillipsc506e302014-09-16 09:43:31 -07002085enum DirChange {
2086 kLeft_DirChange,
2087 kRight_DirChange,
2088 kStraight_DirChange,
2089 kBackwards_DirChange,
2090
2091 kInvalid_DirChange
2092};
2093
2094
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002095static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002096 // The error epsilon was empirically derived; worse case round rects
2097 // with a mid point outset by 2x float epsilon in tests had an error
2098 // of 12.
2099 const int epsilon = 16;
2100 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2101 return false;
2102 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002103 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002104 int aBits = SkFloatAs2sCompliment(compA);
2105 int bBits = SkFloatAs2sCompliment(compB);
2106 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002107}
2108
caryclarkb4216502015-03-02 13:02:34 -08002109static bool approximately_zero_when_compared_to(double x, double y) {
2110 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002111}
2112
caryclarkb4216502015-03-02 13:02:34 -08002113
reed@google.com04863fa2011-05-15 04:08:24 +00002114// only valid for a single contour
2115struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002116 Convexicator()
2117 : fPtCount(0)
2118 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002119 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002120 , fIsFinite(true)
2121 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002122 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002123 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002124 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002125 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002126 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002127 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002128
2129 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002130 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002131 }
2132
2133 SkPath::Convexity getConvexity() const { return fConvexity; }
2134
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002135 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002136 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002137
reed@google.com04863fa2011-05-15 04:08:24 +00002138 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002139 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002140 return;
2141 }
2142
2143 if (0 == fPtCount) {
2144 fCurrPt = pt;
2145 ++fPtCount;
2146 } else {
2147 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002148 SkScalar lengthSqd = vec.lengthSqd();
2149 if (!SkScalarIsFinite(lengthSqd)) {
2150 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002151 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002152 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002153 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002154 fCurrPt = pt;
2155 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002156 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002157 } else {
2158 SkASSERT(fPtCount > 2);
2159 this->addVec(vec);
2160 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002161
reed@google.com85b6e392011-05-15 20:25:17 +00002162 int sx = sign(vec.fX);
2163 int sy = sign(vec.fY);
2164 fDx += (sx != fSx);
2165 fDy += (sy != fSy);
2166 fSx = sx;
2167 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002168
reed@google.com85b6e392011-05-15 20:25:17 +00002169 if (fDx > 3 || fDy > 3) {
2170 fConvexity = SkPath::kConcave_Convexity;
2171 }
reed@google.com04863fa2011-05-15 04:08:24 +00002172 }
2173 }
2174 }
2175
2176 void close() {
2177 if (fPtCount > 2) {
2178 this->addVec(fFirstVec);
2179 }
2180 }
2181
caryclarkb4216502015-03-02 13:02:34 -08002182 DirChange directionChange(const SkVector& curVec) {
2183 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2184
2185 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2186 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2187 largest = SkTMax(largest, -smallest);
2188
2189 if (!almost_equal(largest, largest + cross)) {
2190 int sign = SkScalarSignAsInt(cross);
2191 if (sign) {
2192 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2193 }
2194 }
2195
2196 if (cross) {
2197 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2198 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2199 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2200 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2201 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2202 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2203 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2204 if (sign) {
2205 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2206 }
2207 }
2208 }
2209
2210 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2211 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2212 fLastVec.dot(curVec) < 0.0f) {
2213 return kBackwards_DirChange;
2214 }
2215
2216 return kStraight_DirChange;
2217 }
2218
2219
caryclarkd3d1a982014-12-08 04:57:38 -08002220 bool isFinite() const {
2221 return fIsFinite;
2222 }
2223
caryclark5ccef572015-03-02 10:07:56 -08002224 void setCurve(bool isCurve) {
2225 fIsCurve = isCurve;
2226 }
2227
reed@google.com04863fa2011-05-15 04:08:24 +00002228private:
2229 void addVec(const SkVector& vec) {
2230 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002231 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002232 switch (dir) {
2233 case kLeft_DirChange: // fall through
2234 case kRight_DirChange:
2235 if (kInvalid_DirChange == fExpectedDir) {
2236 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002237 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2238 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002239 } else if (dir != fExpectedDir) {
2240 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002241 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002242 }
2243 fLastVec = vec;
2244 break;
2245 case kStraight_DirChange:
2246 break;
2247 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002248 if (fIsCurve) {
2249 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002250 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002251 }
robertphillipsc506e302014-09-16 09:43:31 -07002252 fLastVec = vec;
2253 break;
2254 case kInvalid_DirChange:
2255 SkFAIL("Use of invalid direction change flag");
2256 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002257 }
2258 }
2259
caryclarkb4216502015-03-02 13:02:34 -08002260 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002261 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002262 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002263 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2264 // value with the current vec is deemed to be of a significant value.
2265 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002267 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002268 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002269 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002270 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002271 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002272 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002273};
2274
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002275SkPath::Convexity SkPath::internalGetConvexity() const {
2276 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002277 SkPoint pts[4];
2278 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002279 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002280
2281 int contourCount = 0;
2282 int count;
2283 Convexicator state;
2284
caryclarkd3d1a982014-12-08 04:57:38 -08002285 if (!isFinite()) {
2286 return kUnknown_Convexity;
2287 }
caryclarke8c56662015-07-14 11:19:26 -07002288 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002289 switch (verb) {
2290 case kMove_Verb:
2291 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002292 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002293 return kConcave_Convexity;
2294 }
2295 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002296 // fall through
2297 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002298 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002299 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002300 break;
caryclark5ccef572015-03-02 10:07:56 -08002301 case kQuad_Verb:
2302 // fall through
2303 case kConic_Verb:
2304 // fall through
2305 case kCubic_Verb:
2306 count = 2 + (kCubic_Verb == verb);
2307 // As an additional enhancement, this could set curve true only
2308 // if the curve is nonlinear
2309 state.setCurve(true);
2310 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002311 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002312 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002313 state.close();
2314 count = 0;
2315 break;
2316 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002317 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002318 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002319 return kConcave_Convexity;
2320 }
2321
2322 for (int i = 1; i <= count; i++) {
2323 state.addPt(pts[i]);
2324 }
2325 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002326 if (!state.isFinite()) {
2327 return kUnknown_Convexity;
2328 }
reed@google.com04863fa2011-05-15 04:08:24 +00002329 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002330 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002331 return kConcave_Convexity;
2332 }
2333 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002334 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002335 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2336 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002337 }
2338 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002339}
reed@google.com69a99432012-01-10 18:00:10 +00002340
2341///////////////////////////////////////////////////////////////////////////////
2342
2343class ContourIter {
2344public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002345 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002346
2347 bool done() const { return fDone; }
2348 // if !done() then these may be called
2349 int count() const { return fCurrPtCount; }
2350 const SkPoint* pts() const { return fCurrPt; }
2351 void next();
2352
2353private:
2354 int fCurrPtCount;
2355 const SkPoint* fCurrPt;
2356 const uint8_t* fCurrVerb;
2357 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002358 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002359 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002360 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002361};
2362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002363ContourIter::ContourIter(const SkPathRef& pathRef) {
2364 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002365 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002366 fCurrPt = pathRef.points();
2367 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002368 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002369 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002370 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002371 this->next();
2372}
2373
2374void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002375 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002376 fDone = true;
2377 }
2378 if (fDone) {
2379 return;
2380 }
2381
2382 // skip pts of prev contour
2383 fCurrPt += fCurrPtCount;
2384
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002385 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002386 int ptCount = 1; // moveTo
2387 const uint8_t* verbs = fCurrVerb;
2388
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002389 for (--verbs; verbs > fStopVerbs; --verbs) {
2390 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002391 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002392 goto CONTOUR_END;
2393 case SkPath::kLine_Verb:
2394 ptCount += 1;
2395 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002396 case SkPath::kConic_Verb:
2397 fCurrConicWeight += 1;
2398 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002399 case SkPath::kQuad_Verb:
2400 ptCount += 2;
2401 break;
2402 case SkPath::kCubic_Verb:
2403 ptCount += 3;
2404 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002405 case SkPath::kClose_Verb:
2406 break;
2407 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002408 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002409 break;
2410 }
2411 }
2412CONTOUR_END:
2413 fCurrPtCount = ptCount;
2414 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002415 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002416}
2417
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002418// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002419static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002420 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2421 // We may get 0 when the above subtracts underflow. We expect this to be
2422 // very rare and lazily promote to double.
2423 if (0 == cross) {
2424 double p0x = SkScalarToDouble(p0.fX);
2425 double p0y = SkScalarToDouble(p0.fY);
2426
2427 double p1x = SkScalarToDouble(p1.fX);
2428 double p1y = SkScalarToDouble(p1.fY);
2429
2430 double p2x = SkScalarToDouble(p2.fX);
2431 double p2y = SkScalarToDouble(p2.fY);
2432
2433 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2434 (p1y - p0y) * (p2x - p0x));
2435
2436 }
2437 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002438}
2439
reed@google.comc1ea60a2012-01-31 15:15:36 +00002440// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002441static int find_max_y(const SkPoint pts[], int count) {
2442 SkASSERT(count > 0);
2443 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002444 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002445 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002446 SkScalar y = pts[i].fY;
2447 if (y > max) {
2448 max = y;
2449 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002450 }
2451 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002452 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002453}
2454
reed@google.comcabaf1d2012-01-11 21:03:05 +00002455static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2456 int i = index;
2457 for (;;) {
2458 i = (i + inc) % n;
2459 if (i == index) { // we wrapped around, so abort
2460 break;
2461 }
2462 if (pts[index] != pts[i]) { // found a different point, success!
2463 break;
2464 }
2465 }
2466 return i;
2467}
2468
reed@google.comc1ea60a2012-01-31 15:15:36 +00002469/**
2470 * Starting at index, and moving forward (incrementing), find the xmin and
2471 * xmax of the contiguous points that have the same Y.
2472 */
2473static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2474 int* maxIndexPtr) {
2475 const SkScalar y = pts[index].fY;
2476 SkScalar min = pts[index].fX;
2477 SkScalar max = min;
2478 int minIndex = index;
2479 int maxIndex = index;
2480 for (int i = index + 1; i < n; ++i) {
2481 if (pts[i].fY != y) {
2482 break;
2483 }
2484 SkScalar x = pts[i].fX;
2485 if (x < min) {
2486 min = x;
2487 minIndex = i;
2488 } else if (x > max) {
2489 max = x;
2490 maxIndex = i;
2491 }
2492 }
2493 *maxIndexPtr = maxIndex;
2494 return minIndex;
2495}
2496
reed026beb52015-06-10 14:23:15 -07002497static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2498 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002499}
2500
reed@google.comac8543f2012-01-30 20:51:25 +00002501/*
2502 * We loop through all contours, and keep the computed cross-product of the
2503 * contour that contained the global y-max. If we just look at the first
2504 * contour, we may find one that is wound the opposite way (correctly) since
2505 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2506 * that is outer most (or at least has the global y-max) before we can consider
2507 * its cross product.
2508 */
reed026beb52015-06-10 14:23:15 -07002509bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002510 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2511 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002512 return true;
2513 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002514
2515 // don't want to pay the cost for computing this if it
2516 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002517 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2518 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002519 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002520 return false;
2521 }
reed@google.com69a99432012-01-10 18:00:10 +00002522
reed026beb52015-06-10 14:23:15 -07002523 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002524
reed@google.comac8543f2012-01-30 20:51:25 +00002525 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002526 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002527 SkScalar ymaxCross = 0;
2528
reed@google.com69a99432012-01-10 18:00:10 +00002529 for (; !iter.done(); iter.next()) {
2530 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002531 if (n < 3) {
2532 continue;
2533 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002534
reed@google.comcabaf1d2012-01-11 21:03:05 +00002535 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002536 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002537 int index = find_max_y(pts, n);
2538 if (pts[index].fY < ymax) {
2539 continue;
2540 }
2541
2542 // If there is more than 1 distinct point at the y-max, we take the
2543 // x-min and x-max of them and just subtract to compute the dir.
2544 if (pts[(index + 1) % n].fY == pts[index].fY) {
2545 int maxIndex;
2546 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2547 if (minIndex == maxIndex) {
2548 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002549 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002550 SkASSERT(pts[minIndex].fY == pts[index].fY);
2551 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2552 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2553 // we just subtract the indices, and let that auto-convert to
2554 // SkScalar, since we just want - or + to signal the direction.
2555 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002556 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002557 TRY_CROSSPROD:
2558 // Find a next and prev index to use for the cross-product test,
2559 // but we try to find pts that form non-zero vectors from pts[index]
2560 //
2561 // Its possible that we can't find two non-degenerate vectors, so
2562 // we have to guard our search (e.g. all the pts could be in the
2563 // same place).
2564
2565 // we pass n - 1 instead of -1 so we don't foul up % operator by
2566 // passing it a negative LH argument.
2567 int prev = find_diff_pt(pts, index, n, n - 1);
2568 if (prev == index) {
2569 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002570 continue;
2571 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002572 int next = find_diff_pt(pts, index, n, 1);
2573 SkASSERT(next != index);
2574 cross = cross_prod(pts[prev], pts[index], pts[next]);
2575 // if we get a zero and the points are horizontal, then we look at the spread in
2576 // x-direction. We really should continue to walk away from the degeneracy until
2577 // there is a divergence.
2578 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2579 // construct the subtract so we get the correct Direction below
2580 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002581 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002582 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002583
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002584 if (cross) {
2585 // record our best guess so far
2586 ymax = pts[index].fY;
2587 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002588 }
2589 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002590 if (ymaxCross) {
2591 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002592 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002593 return true;
2594 } else {
2595 return false;
2596 }
reed@google.comac8543f2012-01-30 20:51:25 +00002597}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002598
2599///////////////////////////////////////////////////////////////////////////////
2600
caryclark9aacd902015-12-14 08:38:09 -08002601static bool between(SkScalar a, SkScalar b, SkScalar c) {
2602 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2603 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2604 return (a - b) * (c - b) <= 0;
2605}
2606
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002607static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2608 SkScalar D, SkScalar t) {
2609 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2610}
2611
2612static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2613 SkScalar t) {
2614 SkScalar A = c3 + 3*(c1 - c2) - c0;
2615 SkScalar B = 3*(c2 - c1 - c1 + c0);
2616 SkScalar C = 3*(c1 - c0);
2617 SkScalar D = c0;
2618 return eval_cubic_coeff(A, B, C, D, t);
2619}
2620
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002621template <size_t N> static void find_minmax(const SkPoint pts[],
2622 SkScalar* minPtr, SkScalar* maxPtr) {
2623 SkScalar min, max;
2624 min = max = pts[0].fX;
2625 for (size_t i = 1; i < N; ++i) {
2626 min = SkMinScalar(min, pts[i].fX);
2627 max = SkMaxScalar(max, pts[i].fX);
2628 }
2629 *minPtr = min;
2630 *maxPtr = max;
2631}
2632
caryclark9cb5d752015-12-18 04:35:24 -08002633static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2634 if (start.fY == end.fY) {
2635 return between(start.fX, x, end.fX) && x != end.fX;
2636 } else {
2637 return x == start.fX && y == start.fY;
2638 }
2639}
2640
caryclark9aacd902015-12-14 08:38:09 -08002641static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002642 SkScalar y0 = pts[0].fY;
2643 SkScalar y3 = pts[3].fY;
2644
2645 int dir = 1;
2646 if (y0 > y3) {
2647 SkTSwap(y0, y3);
2648 dir = -1;
2649 }
2650 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002651 return 0;
2652 }
caryclark9cb5d752015-12-18 04:35:24 -08002653 if (checkOnCurve(x, y, pts[0], pts[3])) {
2654 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002655 return 0;
2656 }
caryclark9cb5d752015-12-18 04:35:24 -08002657 if (y == y3) {
2658 return 0;
2659 }
2660
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002661 // quickreject or quickaccept
2662 SkScalar min, max;
2663 find_minmax<4>(pts, &min, &max);
2664 if (x < min) {
2665 return 0;
2666 }
2667 if (x > max) {
2668 return dir;
2669 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002670
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002671 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002672 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002673 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002674 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002675 if (SkScalarNearlyEqual(xt, x)) {
2676 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2677 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002678 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002679 }
2680 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002681 return xt < x ? dir : 0;
2682}
2683
caryclark9aacd902015-12-14 08:38:09 -08002684static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002685 SkPoint dst[10];
2686 int n = SkChopCubicAtYExtrema(pts, dst);
2687 int w = 0;
2688 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002689 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002690 }
2691 return w;
2692}
2693
caryclark9aacd902015-12-14 08:38:09 -08002694static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2695 SkASSERT(src);
2696 SkASSERT(t >= 0 && t <= 1);
2697 SkScalar src2w = src[2] * w;
2698 SkScalar C = src[0];
2699 SkScalar A = src[4] - 2 * src2w + C;
2700 SkScalar B = 2 * (src2w - C);
2701 return (A * t + B) * t + C;
2702}
2703
2704
2705static double conic_eval_denominator(SkScalar w, SkScalar t) {
2706 SkScalar B = 2 * (w - 1);
2707 SkScalar C = 1;
2708 SkScalar A = -B;
2709 return (A * t + B) * t + C;
2710}
2711
2712static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2713 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002714 SkScalar y0 = pts[0].fY;
2715 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002716
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002717 int dir = 1;
2718 if (y0 > y2) {
2719 SkTSwap(y0, y2);
2720 dir = -1;
2721 }
caryclark9aacd902015-12-14 08:38:09 -08002722 if (y < y0 || y > y2) {
2723 return 0;
2724 }
caryclark9cb5d752015-12-18 04:35:24 -08002725 if (checkOnCurve(x, y, pts[0], pts[2])) {
2726 *onCurveCount += 1;
2727 return 0;
2728 }
caryclark9aacd902015-12-14 08:38:09 -08002729 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002730 return 0;
2731 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002732
caryclark9aacd902015-12-14 08:38:09 -08002733 SkScalar roots[2];
2734 SkScalar A = pts[2].fY;
2735 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2736 SkScalar C = pts[0].fY;
2737 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2738 B -= C; // B = b*w - w * yCept + yCept - a
2739 C -= y;
2740 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2741 SkASSERT(n <= 1);
2742 SkScalar xt;
2743 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002744 // zero roots are returned only when y0 == y
2745 // Need [0] if dir == 1
2746 // and [2] if dir == -1
2747 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002748 } else {
2749 SkScalar t = roots[0];
2750 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2751 }
2752 if (SkScalarNearlyEqual(xt, x)) {
2753 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2754 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002755 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002756 }
2757 }
2758 return xt < x ? dir : 0;
2759}
2760
2761static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2762 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2763 if (y0 == y1) {
2764 return true;
2765 }
2766 if (y0 < y1) {
2767 return y1 <= y2;
2768 } else {
2769 return y1 >= y2;
2770 }
2771}
2772
2773static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2774 int* onCurveCount) {
2775 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002776 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002777 // If the data points are very large, the conic may not be monotonic but may also
2778 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002779 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2780 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2781 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002782 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2783 }
2784 return w;
2785}
2786
2787static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2788 SkScalar y0 = pts[0].fY;
2789 SkScalar y2 = pts[2].fY;
2790
2791 int dir = 1;
2792 if (y0 > y2) {
2793 SkTSwap(y0, y2);
2794 dir = -1;
2795 }
2796 if (y < y0 || y > y2) {
2797 return 0;
2798 }
caryclark9cb5d752015-12-18 04:35:24 -08002799 if (checkOnCurve(x, y, pts[0], pts[2])) {
2800 *onCurveCount += 1;
2801 return 0;
2802 }
caryclark9aacd902015-12-14 08:38:09 -08002803 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002804 return 0;
2805 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 // bounds check on X (not required. is it faster?)
2807#if 0
2808 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2809 return 0;
2810 }
2811#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002812
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 SkScalar roots[2];
2814 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2815 2 * (pts[1].fY - pts[0].fY),
2816 pts[0].fY - y,
2817 roots);
2818 SkASSERT(n <= 1);
2819 SkScalar xt;
2820 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002821 // zero roots are returned only when y0 == y
2822 // Need [0] if dir == 1
2823 // and [2] if dir == -1
2824 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 } else {
2826 SkScalar t = roots[0];
2827 SkScalar C = pts[0].fX;
2828 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2829 SkScalar B = 2 * (pts[1].fX - C);
2830 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2831 }
caryclark9aacd902015-12-14 08:38:09 -08002832 if (SkScalarNearlyEqual(xt, x)) {
2833 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2834 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002835 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002836 }
2837 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002838 return xt < x ? dir : 0;
2839}
2840
caryclark9aacd902015-12-14 08:38:09 -08002841static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002842 SkPoint dst[5];
2843 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002844
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2846 n = SkChopQuadAtYExtrema(pts, dst);
2847 pts = dst;
2848 }
caryclark9aacd902015-12-14 08:38:09 -08002849 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002850 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002851 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 }
2853 return w;
2854}
2855
caryclark9aacd902015-12-14 08:38:09 -08002856static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 SkScalar x0 = pts[0].fX;
2858 SkScalar y0 = pts[0].fY;
2859 SkScalar x1 = pts[1].fX;
2860 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002863
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 int dir = 1;
2865 if (y0 > y1) {
2866 SkTSwap(y0, y1);
2867 dir = -1;
2868 }
caryclark9aacd902015-12-14 08:38:09 -08002869 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002870 return 0;
2871 }
caryclark9cb5d752015-12-18 04:35:24 -08002872 if (checkOnCurve(x, y, pts[0], pts[1])) {
2873 *onCurveCount += 1;
2874 return 0;
2875 }
2876 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002877 return 0;
2878 }
2879 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
caryclark9aacd902015-12-14 08:38:09 -08002881 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002882 // zero cross means the point is on the line, and since the case where
2883 // y of the query point is at the end point is handled above, we can be
2884 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002885 if (x != x1 || y != pts[1].fY) {
2886 *onCurveCount += 1;
2887 }
caryclark9aacd902015-12-14 08:38:09 -08002888 dir = 0;
2889 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890 dir = 0;
2891 }
2892 return dir;
2893}
2894
caryclark9aacd902015-12-14 08:38:09 -08002895static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
2896 SkTDArray<SkVector>* tangents) {
2897 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
2898 && !between(pts[2].fY, y, pts[3].fY)) {
2899 return;
2900 }
2901 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
2902 && !between(pts[2].fX, x, pts[3].fX)) {
2903 return;
2904 }
2905 SkPoint dst[10];
2906 int n = SkChopCubicAtYExtrema(pts, dst);
2907 for (int i = 0; i <= n; ++i) {
2908 SkPoint* c = &dst[i * 3];
2909 SkScalar t;
2910 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
2911 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
2912 if (!SkScalarNearlyEqual(x, xt)) {
2913 continue;
2914 }
2915 SkVector tangent;
2916 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
2917 tangents->push(tangent);
2918 }
2919}
2920
2921static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
2922 SkTDArray<SkVector>* tangents) {
2923 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
2924 return;
2925 }
2926 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
2927 return;
2928 }
2929 SkScalar roots[2];
2930 SkScalar A = pts[2].fY;
2931 SkScalar B = pts[1].fY * w - y * w + y;
2932 SkScalar C = pts[0].fY;
2933 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2934 B -= C; // B = b*w - w * yCept + yCept - a
2935 C -= y;
2936 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2937 for (int index = 0; index < n; ++index) {
2938 SkScalar t = roots[index];
2939 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
2940 if (!SkScalarNearlyEqual(x, xt)) {
2941 continue;
2942 }
2943 SkConic conic(pts, w);
2944 tangents->push(conic.evalTangentAt(t));
2945 }
2946}
2947
2948static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
2949 SkTDArray<SkVector>* tangents) {
2950 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
2951 return;
2952 }
2953 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
2954 return;
2955 }
2956 SkScalar roots[2];
2957 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2958 2 * (pts[1].fY - pts[0].fY),
2959 pts[0].fY - y,
2960 roots);
2961 for (int index = 0; index < n; ++index) {
2962 SkScalar t = roots[index];
2963 SkScalar C = pts[0].fX;
2964 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2965 SkScalar B = 2 * (pts[1].fX - C);
2966 SkScalar xt = (A * t + B) * t + C;
2967 if (!SkScalarNearlyEqual(x, xt)) {
2968 continue;
2969 }
2970 tangents->push(SkEvalQuadTangentAt(pts, t));
2971 }
2972}
2973
2974static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
2975 SkTDArray<SkVector>* tangents) {
2976 SkScalar y0 = pts[0].fY;
2977 SkScalar y1 = pts[1].fY;
2978 if (!between(y0, y, y1)) {
2979 return;
2980 }
2981 SkScalar x0 = pts[0].fX;
2982 SkScalar x1 = pts[1].fX;
2983 if (!between(x0, x, x1)) {
2984 return;
2985 }
2986 SkScalar dx = x1 - x0;
2987 SkScalar dy = y1 - y0;
2988 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
2989 return;
2990 }
2991 SkVector v;
2992 v.set(dx, dy);
2993 tangents->push(v);
2994}
2995
reed@google.com4db592c2013-10-30 17:39:43 +00002996static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2997 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2998}
2999
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000bool SkPath::contains(SkScalar x, SkScalar y) const {
3001 bool isInverse = this->isInverseFillType();
3002 if (this->isEmpty()) {
3003 return isInverse;
3004 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003005
reed@google.com4db592c2013-10-30 17:39:43 +00003006 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 return isInverse;
3008 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003009
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 SkPath::Iter iter(*this, true);
3011 bool done = false;
3012 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003013 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003014 do {
3015 SkPoint pts[4];
3016 switch (iter.next(pts, false)) {
3017 case SkPath::kMove_Verb:
3018 case SkPath::kClose_Verb:
3019 break;
3020 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003021 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 break;
3023 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003024 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003026 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003027 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003028 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003029 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003030 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 break;
3032 case SkPath::kDone_Verb:
3033 done = true;
3034 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003035 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003036 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003037 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3038 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3039 if (evenOddFill) {
3040 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003041 }
caryclark9aacd902015-12-14 08:38:09 -08003042 if (w) {
3043 return !isInverse;
3044 }
3045 if (onCurveCount <= 1) {
3046 return SkToBool(onCurveCount) ^ isInverse;
3047 }
3048 if ((onCurveCount & 1) || evenOddFill) {
3049 return SkToBool(onCurveCount & 1) ^ isInverse;
3050 }
3051 // If the point touches an even number of curves, and the fill is winding, check for
3052 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3053 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003054 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003055 SkTDArray<SkVector> tangents;
3056 do {
3057 SkPoint pts[4];
3058 int oldCount = tangents.count();
3059 switch (iter.next(pts, false)) {
3060 case SkPath::kMove_Verb:
3061 case SkPath::kClose_Verb:
3062 break;
3063 case SkPath::kLine_Verb:
3064 tangent_line(pts, x, y, &tangents);
3065 break;
3066 case SkPath::kQuad_Verb:
3067 tangent_quad(pts, x, y, &tangents);
3068 break;
3069 case SkPath::kConic_Verb:
3070 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3071 break;
3072 case SkPath::kCubic_Verb:
3073 tangent_cubic(pts, x, y, &tangents);
3074 break;
3075 case SkPath::kDone_Verb:
3076 done = true;
3077 break;
3078 }
3079 if (tangents.count() > oldCount) {
3080 int last = tangents.count() - 1;
3081 const SkVector& tangent = tangents[last];
3082 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3083 tangents.remove(last);
3084 } else {
3085 for (int index = 0; index < last; ++index) {
3086 const SkVector& test = tangents[index];
3087 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003088 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3089 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003090 tangents.remove(last);
3091 tangents.removeShuffle(index);
3092 break;
3093 }
3094 }
3095 }
3096 }
3097 } while (!done);
3098 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003099}
fmalitaaa0df4e2015-12-01 09:13:23 -08003100
3101int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3102 SkScalar w, SkPoint pts[], int pow2) {
3103 const SkConic conic(p0, p1, p2, w);
3104 return conic.chopIntoQuadsPOW2(pts, pow2);
3105}