blob: 40ca50b5caf99b4c93f6f9380a9ea62a9f2b7793 [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);
djsollenaa97a842016-01-22 06:50:25 -0800278 SK_ALWAYSBREAK(2 == count);
reed220f9262014-12-17 08:21:04 -0800279
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
caryclark55d49052016-01-23 05:07:04 -08001260// This converts the SVG arc to conics.
1261// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1262// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1263// See also SVG implementation notes:
1264// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1265// Note that arcSweep bool value is flipped from the original implementation.
1266void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1267 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
1268 SkPoint srcPts[2];
1269 this->getLastPt(&srcPts[0]);
1270 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1271 // joining the endpoints.
1272 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1273 if (!rx || !ry) {
1274 return;
1275 }
1276 // If the current point and target point for the arc are identical, it should be treated as a
1277 // zero length path. This ensures continuity in animations.
1278 srcPts[1].set(x, y);
1279 if (srcPts[0] == srcPts[1]) {
1280 return;
1281 }
1282 rx = SkScalarAbs(rx);
1283 ry = SkScalarAbs(ry);
1284 SkVector midPointDistance = srcPts[0] - srcPts[1];
1285 midPointDistance *= 0.5f;
1286
1287 SkMatrix pointTransform;
1288 pointTransform.setRotate(-angle);
1289
1290 SkPoint transformedMidPoint;
1291 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1292 SkScalar squareRx = rx * rx;
1293 SkScalar squareRy = ry * ry;
1294 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1295 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1296
1297 // Check if the radii are big enough to draw the arc, scale radii if not.
1298 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1299 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1300 if (radiiScale > 1) {
1301 radiiScale = SkScalarSqrt(radiiScale);
1302 rx *= radiiScale;
1303 ry *= radiiScale;
1304 }
1305
1306 pointTransform.setScale(1 / rx, 1 / ry);
1307 pointTransform.preRotate(-angle);
1308
1309 SkPoint unitPts[2];
1310 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1311 SkVector delta = unitPts[1] - unitPts[0];
1312
1313 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1314 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1315
1316 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1317 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1318 scaleFactor = -scaleFactor;
1319 }
1320 delta.scale(scaleFactor);
1321 SkPoint centerPoint = unitPts[0] + unitPts[1];
1322 centerPoint *= 0.5f;
1323 centerPoint.offset(-delta.fY, delta.fX);
1324 unitPts[0] -= centerPoint;
1325 unitPts[1] -= centerPoint;
1326 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1327 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1328 SkScalar thetaArc = theta2 - theta1;
1329 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1330 thetaArc += SK_ScalarPI * 2;
1331 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1332 thetaArc -= SK_ScalarPI * 2;
1333 }
1334 pointTransform.setRotate(angle);
1335 pointTransform.preScale(rx, ry);
1336
1337 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1338 SkScalar thetaWidth = thetaArc / segments;
1339 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1340 if (!SkScalarIsFinite(t)) {
1341 return;
1342 }
1343 SkScalar startTheta = theta1;
1344 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1345 for (int i = 0; i < segments; ++i) {
1346 SkScalar endTheta = startTheta + thetaWidth;
1347 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1348
1349 unitPts[1].set(cosEndTheta, sinEndTheta);
1350 unitPts[1] += centerPoint;
1351 unitPts[0] = unitPts[1];
1352 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1353 SkPoint mapped[2];
1354 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1355 this->conicTo(mapped[0], mapped[1], w);
1356 startTheta = endTheta;
1357 }
1358}
1359
1360void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1361 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1362 SkPoint currentPoint;
1363 this->getLastPt(&currentPoint);
1364 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1365}
1366
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001367void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001368 if (oval.isEmpty() || 0 == sweepAngle) {
1369 return;
1370 }
1371
1372 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1373
1374 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1375 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001376 } else {
1377 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001378 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379}
1380
1381/*
1382 Need to handle the case when the angle is sharp, and our computed end-points
1383 for the arc go behind pt1 and/or p2...
1384*/
reedc7789042015-01-29 12:59:11 -08001385void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001386 if (radius == 0) {
1387 this->lineTo(x1, y1);
1388 return;
1389 }
1390
1391 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001392
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393 // need to know our prev pt so we can construct tangent vectors
1394 {
1395 SkPoint start;
1396 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001397 // Handle degenerate cases by adding a line to the first point and
1398 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 before.setNormalize(x1 - start.fX, y1 - start.fY);
1400 after.setNormalize(x2 - x1, y2 - y1);
1401 }
reed@google.comabf15c12011-01-18 20:35:51 +00001402
reed@android.com8a1c16f2008-12-17 15:59:43 +00001403 SkScalar cosh = SkPoint::DotProduct(before, after);
1404 SkScalar sinh = SkPoint::CrossProduct(before, after);
1405
1406 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001407 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001408 return;
1409 }
reed@google.comabf15c12011-01-18 20:35:51 +00001410
caryclark88651ae2016-01-20 11:55:11 -08001411 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001412
1413 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1414 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001415#ifndef SK_SUPPORT_LEGACY_ARCTO
1416 after.setLength(dist);
1417 this->lineTo(xx, yy);
1418 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1419 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
1420#else
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421 SkRotationDirection arcDir;
1422
1423 // now turn before/after into normals
1424 if (sinh > 0) {
1425 before.rotateCCW();
1426 after.rotateCCW();
1427 arcDir = kCW_SkRotationDirection;
1428 } else {
1429 before.rotateCW();
1430 after.rotateCW();
1431 arcDir = kCCW_SkRotationDirection;
1432 }
1433
1434 SkMatrix matrix;
1435 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001436
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 matrix.setScale(radius, radius);
1438 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1439 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001440
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001442
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 this->incReserve(count);
1444 // [xx,yy] == pts[0]
1445 this->lineTo(xx, yy);
1446 for (int i = 1; i < count; i += 2) {
1447 this->quadTo(pts[i], pts[i+1]);
1448 }
caryclark88651ae2016-01-20 11:55:11 -08001449#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001450}
1451
1452///////////////////////////////////////////////////////////////////////////////
1453
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001454void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 SkMatrix matrix;
1456
1457 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001458 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459}
1460
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001461void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001462 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001464 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 SkPoint pts[4];
1466 Verb verb;
1467
1468 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001469 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 while ((verb = iter.next(pts)) != kDone_Verb) {
1471 switch (verb) {
1472 case kMove_Verb:
1473 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001474 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1475 injectMoveToIfNeeded(); // In case last contour is closed
1476 this->lineTo(pts[0]);
1477 } else {
1478 this->moveTo(pts[0]);
1479 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 break;
1481 case kLine_Verb:
1482 proc(matrix, &pts[1], &pts[1], 1);
1483 this->lineTo(pts[1]);
1484 break;
1485 case kQuad_Verb:
1486 proc(matrix, &pts[1], &pts[1], 2);
1487 this->quadTo(pts[1], pts[2]);
1488 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001489 case kConic_Verb:
1490 proc(matrix, &pts[1], &pts[1], 2);
1491 this->conicTo(pts[1], pts[2], iter.conicWeight());
1492 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 case kCubic_Verb:
1494 proc(matrix, &pts[1], &pts[1], 3);
1495 this->cubicTo(pts[1], pts[2], pts[3]);
1496 break;
1497 case kClose_Verb:
1498 this->close();
1499 break;
1500 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001501 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001503 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504 }
1505}
1506
1507///////////////////////////////////////////////////////////////////////////////
1508
reed@google.com277c3f82013-05-31 15:17:50 +00001509static int pts_in_verb(unsigned verb) {
1510 static const uint8_t gPtsInVerb[] = {
1511 1, // kMove
1512 1, // kLine
1513 2, // kQuad
1514 2, // kConic
1515 3, // kCubic
1516 0, // kClose
1517 0 // kDone
1518 };
1519
1520 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1521 return gPtsInVerb[verb];
1522}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524// ignore the last point of the 1st contour
1525void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001526 int i, vcount = path.fPathRef->countVerbs();
1527 // exit early if the path is empty, or just has a moveTo.
1528 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529 return;
1530 }
1531
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001532 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001534 const uint8_t* verbs = path.fPathRef->verbs();
1535 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001536 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001538 SkASSERT(verbs[~0] == kMove_Verb);
1539 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001540 unsigned v = verbs[~i];
1541 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 if (n == 0) {
1543 break;
1544 }
1545 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001546 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 }
1548
1549 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001550 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 case kLine_Verb:
1552 this->lineTo(pts[-1].fX, pts[-1].fY);
1553 break;
1554 case kQuad_Verb:
1555 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1556 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001557 case kConic_Verb:
1558 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1559 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 case kCubic_Verb:
1561 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1562 pts[-3].fX, pts[-3].fY);
1563 break;
1564 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001565 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566 break;
1567 }
reed@google.com277c3f82013-05-31 15:17:50 +00001568 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 }
1570}
1571
reed@google.com63d73742012-01-10 15:33:12 +00001572void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001573 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001574
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001575 const SkPoint* pts = src.fPathRef->pointsEnd();
1576 // we will iterator through src's verbs backwards
1577 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1578 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001579 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001580
1581 bool needMove = true;
1582 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001583 while (verbs < verbsEnd) {
1584 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001585 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001586
1587 if (needMove) {
1588 --pts;
1589 this->moveTo(pts->fX, pts->fY);
1590 needMove = false;
1591 }
1592 pts -= n;
1593 switch (v) {
1594 case kMove_Verb:
1595 if (needClose) {
1596 this->close();
1597 needClose = false;
1598 }
1599 needMove = true;
1600 pts += 1; // so we see the point in "if (needMove)" above
1601 break;
1602 case kLine_Verb:
1603 this->lineTo(pts[0]);
1604 break;
1605 case kQuad_Verb:
1606 this->quadTo(pts[1], pts[0]);
1607 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001608 case kConic_Verb:
1609 this->conicTo(pts[1], pts[0], *--conicWeights);
1610 break;
reed@google.com63d73742012-01-10 15:33:12 +00001611 case kCubic_Verb:
1612 this->cubicTo(pts[2], pts[1], pts[0]);
1613 break;
1614 case kClose_Verb:
1615 needClose = true;
1616 break;
1617 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001618 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001619 }
1620 }
1621}
1622
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623///////////////////////////////////////////////////////////////////////////////
1624
1625void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1626 SkMatrix matrix;
1627
1628 matrix.setTranslate(dx, dy);
1629 this->transform(matrix, dst);
1630}
1631
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1633 int level = 2) {
1634 if (--level >= 0) {
1635 SkPoint tmp[7];
1636
1637 SkChopCubicAtHalf(pts, tmp);
1638 subdivide_cubic_to(path, &tmp[0], level);
1639 subdivide_cubic_to(path, &tmp[3], level);
1640 } else {
1641 path->cubicTo(pts[1], pts[2], pts[3]);
1642 }
1643}
1644
1645void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1646 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001647 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 dst = (SkPath*)this;
1649 }
1650
tomhudson@google.com8d430182011-06-06 19:11:19 +00001651 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 SkPath tmp;
1653 tmp.fFillType = fFillType;
1654
1655 SkPath::Iter iter(*this, false);
1656 SkPoint pts[4];
1657 SkPath::Verb verb;
1658
reed@google.com4a3b7142012-05-16 17:16:46 +00001659 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001660 switch (verb) {
1661 case kMove_Verb:
1662 tmp.moveTo(pts[0]);
1663 break;
1664 case kLine_Verb:
1665 tmp.lineTo(pts[1]);
1666 break;
1667 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001668 // promote the quad to a conic
1669 tmp.conicTo(pts[1], pts[2],
1670 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001672 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001673 tmp.conicTo(pts[1], pts[2],
1674 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001675 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 case kCubic_Verb:
1677 subdivide_cubic_to(&tmp, pts);
1678 break;
1679 case kClose_Verb:
1680 tmp.close();
1681 break;
1682 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001683 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001684 break;
1685 }
1686 }
1687
1688 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001689 SkPathRef::Editor ed(&dst->fPathRef);
1690 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001691 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1694
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001697 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001698 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001700
reed026beb52015-06-10 14:23:15 -07001701 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1702 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001703 } else {
1704 SkScalar det2x2 =
1705 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1706 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1707 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001708 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1709 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001710 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001711 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001712 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001713 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001714 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001715 }
1716 }
1717
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718 SkDEBUGCODE(dst->validate();)
1719 }
1720}
1721
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722///////////////////////////////////////////////////////////////////////////////
1723///////////////////////////////////////////////////////////////////////////////
1724
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001726 kEmptyContour_SegmentState, // The current contour is empty. We may be
1727 // starting processing or we may have just
1728 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001729 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1730 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1731 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732};
1733
1734SkPath::Iter::Iter() {
1735#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001736 fPts = nullptr;
1737 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001740 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741#endif
1742 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001743 fVerbs = nullptr;
1744 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 fNeedClose = false;
1746}
1747
1748SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1749 this->setPath(path, forceClose);
1750}
1751
1752void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001753 fPts = path.fPathRef->points();
1754 fVerbs = path.fPathRef->verbs();
1755 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001756 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001758 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 fForceClose = SkToU8(forceClose);
1760 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001761 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762}
1763
1764bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001765 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 return false;
1767 }
1768 if (fForceClose) {
1769 return true;
1770 }
1771
1772 const uint8_t* verbs = fVerbs;
1773 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001774
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001775 if (kMove_Verb == *(verbs - 1)) {
1776 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 }
1778
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001779 while (verbs > stop) {
1780 // verbs points one beyond the current verb, decrement first.
1781 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 if (kMove_Verb == v) {
1783 break;
1784 }
1785 if (kClose_Verb == v) {
1786 return true;
1787 }
1788 }
1789 return false;
1790}
1791
1792SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001793 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001795 // A special case: if both points are NaN, SkPoint::operation== returns
1796 // false, but the iterator expects that they are treated as the same.
1797 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001798 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1799 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001800 return kClose_Verb;
1801 }
1802
reed@google.com9e25dbf2012-05-15 17:05:38 +00001803 pts[0] = fLastPt;
1804 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 fLastPt = fMoveTo;
1806 fCloseLine = true;
1807 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001808 } else {
1809 pts[0] = fMoveTo;
1810 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812}
1813
reed@google.com9e25dbf2012-05-15 17:05:38 +00001814const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 if (fSegmentState == kAfterMove_SegmentState) {
1816 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001818 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1821 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001822 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824}
1825
caryclarke8c56662015-07-14 11:19:26 -07001826void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 // We need to step over anything that will not move the current draw point
1828 // forward before the next move is seen
1829 const uint8_t* lastMoveVerb = 0;
1830 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001831 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001832 SkPoint lastPt = fLastPt;
1833 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001834 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 switch (verb) {
1836 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 // Keep a record of this most recent move
1838 lastMoveVerb = fVerbs;
1839 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001840 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001841 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001842 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 fPts++;
1844 break;
1845
1846 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001847 // A close when we are in a segment is always valid except when it
1848 // follows a move which follows a segment.
1849 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 return;
1851 }
1852 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001853 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 break;
1855
1856 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001857 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 if (lastMoveVerb) {
1859 fVerbs = lastMoveVerb;
1860 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001861 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 return;
1863 }
1864 return;
1865 }
1866 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fPts++;
1869 break;
1870
reed@google.com277c3f82013-05-31 15:17:50 +00001871 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001873 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 if (lastMoveVerb) {
1875 fVerbs = lastMoveVerb;
1876 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001877 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 return;
1879 }
1880 return;
1881 }
1882 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001885 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 break;
1887
1888 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001889 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 if (lastMoveVerb) {
1891 fVerbs = lastMoveVerb;
1892 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001893 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 return;
1895 }
1896 return;
1897 }
1898 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001899 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 fPts += 3;
1901 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001902
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001904 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 }
1906 }
1907}
1908
reed@google.com4a3b7142012-05-16 17:16:46 +00001909SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001910 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 // Close the curve if requested and if there is some curve to close
1914 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 return kLine_Verb;
1917 }
1918 fNeedClose = false;
1919 return kClose_Verb;
1920 }
1921 return kDone_Verb;
1922 }
1923
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001924 // fVerbs is one beyond the current verb, decrement first
1925 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001926 const SkPoint* SK_RESTRICT srcPts = fPts;
1927 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001928
1929 switch (verb) {
1930 case kMove_Verb:
1931 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 verb = this->autoClose(pts);
1934 if (verb == kClose_Verb) {
1935 fNeedClose = false;
1936 }
1937 return (Verb)verb;
1938 }
1939 if (fVerbs == fVerbStop) { // might be a trailing moveto
1940 return kDone_Verb;
1941 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001942 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001943 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001945 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001947 fNeedClose = fForceClose;
1948 break;
1949 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001950 pts[0] = this->cons_moveTo();
1951 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 fLastPt = srcPts[0];
1953 fCloseLine = false;
1954 srcPts += 1;
1955 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001956 case kConic_Verb:
1957 fConicWeights += 1;
1958 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001960 pts[0] = this->cons_moveTo();
1961 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 fLastPt = srcPts[1];
1963 srcPts += 2;
1964 break;
1965 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 pts[0] = this->cons_moveTo();
1967 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968 fLastPt = srcPts[2];
1969 srcPts += 3;
1970 break;
1971 case kClose_Verb:
1972 verb = this->autoClose(pts);
1973 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001974 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975 } else {
1976 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001977 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001978 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 break;
1981 }
1982 fPts = srcPts;
1983 return (Verb)verb;
1984}
1985
1986///////////////////////////////////////////////////////////////////////////////
1987
reed@android.com8a1c16f2008-12-17 15:59:43 +00001988/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001989 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990*/
1991
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001992size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993 SkDEBUGCODE(this->validate();)
1994
halcanary96fcdcc2015-08-27 07:41:13 -07001995 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001996 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001997 return SkAlign4(byteCount);
1998 }
1999
2000 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002001
robertphillips@google.com466310d2013-12-03 16:43:54 +00002002 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002003 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002004 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002005 (fIsVolatile << kIsVolatile_SerializationShift) |
2006 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002007
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002008 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002009
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002010 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002011
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002012 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002013 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014}
2015
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002016size_t SkPath::readFromMemory(const void* storage, size_t length) {
2017 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002018
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002019 int32_t packed;
2020 if (!buffer.readS32(&packed)) {
2021 return 0;
2022 }
2023
reed8f086022015-06-11 14:22:19 -07002024 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07002025
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002026 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2027 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002028 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002029 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002030 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002031 if (!pathRef) {
2032 return 0;
2033 }
2034
2035 fPathRef.reset(pathRef);
2036 SkDEBUGCODE(this->validate();)
2037 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002038
reed8f086022015-06-11 14:22:19 -07002039 // compatibility check
2040 if (version < kPathPrivFirstDirection_Version) {
2041 switch (dir) { // old values
2042 case 0:
2043 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2044 break;
2045 case 1:
2046 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2047 break;
2048 case 2:
2049 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2050 break;
2051 default:
2052 SkASSERT(false);
2053 }
2054 } else {
2055 fFirstDirection = dir;
2056 }
2057
ajumaf8aec582016-01-13 13:46:31 -08002058 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002059}
2060
2061///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002062
reede05fed02014-12-15 07:59:53 -08002063#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002064#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002065
reed@google.com51bbe752013-01-17 22:07:50 +00002066static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002067 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002068 str->append(label);
2069 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002070
reed@google.com51bbe752013-01-17 22:07:50 +00002071 const SkScalar* values = &pts[0].fX;
2072 count *= 2;
2073
2074 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002075 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002076 if (i < count - 1) {
2077 str->append(", ");
2078 }
2079 }
reed@google.com277c3f82013-05-31 15:17:50 +00002080 if (conicWeight >= 0) {
2081 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002082 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002083 }
caryclark08fa28c2014-10-23 13:08:56 -07002084 str->append(");");
reede05fed02014-12-15 07:59:53 -08002085 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002086 str->append(" // ");
2087 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002088 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002089 if (i < count - 1) {
2090 str->append(", ");
2091 }
2092 }
2093 if (conicWeight >= 0) {
2094 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002095 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002096 }
2097 }
2098 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002099}
2100
caryclarke9562592014-09-15 09:26:09 -07002101void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002102 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 Iter iter(*this, forceClose);
2104 SkPoint pts[4];
2105 Verb verb;
2106
caryclark66a5d8b2014-06-24 08:30:15 -07002107 if (!wStream) {
2108 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2109 }
reed@google.com51bbe752013-01-17 22:07:50 +00002110 SkString builder;
2111
reed@google.com4a3b7142012-05-16 17:16:46 +00002112 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113 switch (verb) {
2114 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002115 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 break;
2117 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002118 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 break;
2120 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002121 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002123 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002124 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002125 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002127 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 break;
2129 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002130 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 break;
2132 default:
2133 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2134 verb = kDone_Verb; // stop the loop
2135 break;
2136 }
caryclark1049f122015-04-20 08:31:59 -07002137 if (!wStream && builder.size()) {
2138 SkDebugf("%s", builder.c_str());
2139 builder.reset();
2140 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 }
caryclark66a5d8b2014-06-24 08:30:15 -07002142 if (wStream) {
2143 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002144 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145}
2146
reed@android.come522ca52009-11-23 20:10:41 +00002147void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002148 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002149}
2150
2151void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002152 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002153}
2154
2155#ifdef SK_DEBUG
2156void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002157 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002158
djsollen@google.com077348c2012-10-22 20:23:32 +00002159#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002160 if (!fBoundsIsDirty) {
2161 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002162
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002163 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002164 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002165
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002166 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002167 // if we're empty, fBounds may be empty but translated, so we can't
2168 // necessarily compare to bounds directly
2169 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2170 // be [2, 2, 2, 2]
2171 SkASSERT(bounds.isEmpty());
2172 SkASSERT(fBounds.isEmpty());
2173 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002174 if (bounds.isEmpty()) {
2175 SkASSERT(fBounds.isEmpty());
2176 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002177 if (!fBounds.isEmpty()) {
2178 SkASSERT(fBounds.contains(bounds));
2179 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002180 }
reed@android.come522ca52009-11-23 20:10:41 +00002181 }
2182 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002183#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002184}
djsollen@google.com077348c2012-10-22 20:23:32 +00002185#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002186
reed@google.com04863fa2011-05-15 04:08:24 +00002187///////////////////////////////////////////////////////////////////////////////
2188
reed@google.com0b7b9822011-05-16 12:29:27 +00002189static int sign(SkScalar x) { return x < 0; }
2190#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002191
robertphillipsc506e302014-09-16 09:43:31 -07002192enum DirChange {
2193 kLeft_DirChange,
2194 kRight_DirChange,
2195 kStraight_DirChange,
2196 kBackwards_DirChange,
2197
2198 kInvalid_DirChange
2199};
2200
2201
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002202static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002203 // The error epsilon was empirically derived; worse case round rects
2204 // with a mid point outset by 2x float epsilon in tests had an error
2205 // of 12.
2206 const int epsilon = 16;
2207 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2208 return false;
2209 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002210 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002211 int aBits = SkFloatAs2sCompliment(compA);
2212 int bBits = SkFloatAs2sCompliment(compB);
2213 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002214}
2215
caryclarkb4216502015-03-02 13:02:34 -08002216static bool approximately_zero_when_compared_to(double x, double y) {
2217 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002218}
2219
caryclarkb4216502015-03-02 13:02:34 -08002220
reed@google.com04863fa2011-05-15 04:08:24 +00002221// only valid for a single contour
2222struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002223 Convexicator()
2224 : fPtCount(0)
2225 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002226 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002227 , fIsFinite(true)
2228 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002229 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002230 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002231 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002232 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002233 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002234 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002235
2236 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002237 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002238 }
2239
2240 SkPath::Convexity getConvexity() const { return fConvexity; }
2241
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002242 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002243 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002244
reed@google.com04863fa2011-05-15 04:08:24 +00002245 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002246 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002247 return;
2248 }
2249
2250 if (0 == fPtCount) {
2251 fCurrPt = pt;
2252 ++fPtCount;
2253 } else {
2254 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002255 SkScalar lengthSqd = vec.lengthSqd();
2256 if (!SkScalarIsFinite(lengthSqd)) {
2257 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002258 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002259 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002260 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002261 fCurrPt = pt;
2262 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002263 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002264 } else {
2265 SkASSERT(fPtCount > 2);
2266 this->addVec(vec);
2267 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002268
reed@google.com85b6e392011-05-15 20:25:17 +00002269 int sx = sign(vec.fX);
2270 int sy = sign(vec.fY);
2271 fDx += (sx != fSx);
2272 fDy += (sy != fSy);
2273 fSx = sx;
2274 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002275
reed@google.com85b6e392011-05-15 20:25:17 +00002276 if (fDx > 3 || fDy > 3) {
2277 fConvexity = SkPath::kConcave_Convexity;
2278 }
reed@google.com04863fa2011-05-15 04:08:24 +00002279 }
2280 }
2281 }
2282
2283 void close() {
2284 if (fPtCount > 2) {
2285 this->addVec(fFirstVec);
2286 }
2287 }
2288
caryclarkb4216502015-03-02 13:02:34 -08002289 DirChange directionChange(const SkVector& curVec) {
2290 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2291
2292 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2293 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2294 largest = SkTMax(largest, -smallest);
2295
2296 if (!almost_equal(largest, largest + cross)) {
2297 int sign = SkScalarSignAsInt(cross);
2298 if (sign) {
2299 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2300 }
2301 }
2302
2303 if (cross) {
2304 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2305 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2306 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2307 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2308 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2309 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2310 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2311 if (sign) {
2312 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2313 }
2314 }
2315 }
2316
2317 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2318 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2319 fLastVec.dot(curVec) < 0.0f) {
2320 return kBackwards_DirChange;
2321 }
2322
2323 return kStraight_DirChange;
2324 }
2325
2326
caryclarkd3d1a982014-12-08 04:57:38 -08002327 bool isFinite() const {
2328 return fIsFinite;
2329 }
2330
caryclark5ccef572015-03-02 10:07:56 -08002331 void setCurve(bool isCurve) {
2332 fIsCurve = isCurve;
2333 }
2334
reed@google.com04863fa2011-05-15 04:08:24 +00002335private:
2336 void addVec(const SkVector& vec) {
2337 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002338 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002339 switch (dir) {
2340 case kLeft_DirChange: // fall through
2341 case kRight_DirChange:
2342 if (kInvalid_DirChange == fExpectedDir) {
2343 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002344 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2345 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002346 } else if (dir != fExpectedDir) {
2347 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002348 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002349 }
2350 fLastVec = vec;
2351 break;
2352 case kStraight_DirChange:
2353 break;
2354 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002355 if (fIsCurve) {
2356 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002357 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002358 }
robertphillipsc506e302014-09-16 09:43:31 -07002359 fLastVec = vec;
2360 break;
2361 case kInvalid_DirChange:
2362 SkFAIL("Use of invalid direction change flag");
2363 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002364 }
2365 }
2366
caryclarkb4216502015-03-02 13:02:34 -08002367 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002368 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002369 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002370 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2371 // value with the current vec is deemed to be of a significant value.
2372 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002373 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002374 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002375 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002376 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002377 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002378 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002379 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002380};
2381
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002382SkPath::Convexity SkPath::internalGetConvexity() const {
2383 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002384 SkPoint pts[4];
2385 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002386 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002387
2388 int contourCount = 0;
2389 int count;
2390 Convexicator state;
2391
caryclarkd3d1a982014-12-08 04:57:38 -08002392 if (!isFinite()) {
2393 return kUnknown_Convexity;
2394 }
caryclarke8c56662015-07-14 11:19:26 -07002395 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002396 switch (verb) {
2397 case kMove_Verb:
2398 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002399 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002400 return kConcave_Convexity;
2401 }
2402 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002403 // fall through
2404 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002405 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002406 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002407 break;
caryclark5ccef572015-03-02 10:07:56 -08002408 case kQuad_Verb:
2409 // fall through
2410 case kConic_Verb:
2411 // fall through
2412 case kCubic_Verb:
2413 count = 2 + (kCubic_Verb == verb);
2414 // As an additional enhancement, this could set curve true only
2415 // if the curve is nonlinear
2416 state.setCurve(true);
2417 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002419 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002420 state.close();
2421 count = 0;
2422 break;
2423 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002424 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002425 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002426 return kConcave_Convexity;
2427 }
2428
2429 for (int i = 1; i <= count; i++) {
2430 state.addPt(pts[i]);
2431 }
2432 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002433 if (!state.isFinite()) {
2434 return kUnknown_Convexity;
2435 }
reed@google.com04863fa2011-05-15 04:08:24 +00002436 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002437 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002438 return kConcave_Convexity;
2439 }
2440 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002441 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002442 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2443 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002444 }
2445 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002446}
reed@google.com69a99432012-01-10 18:00:10 +00002447
2448///////////////////////////////////////////////////////////////////////////////
2449
2450class ContourIter {
2451public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002452 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002453
2454 bool done() const { return fDone; }
2455 // if !done() then these may be called
2456 int count() const { return fCurrPtCount; }
2457 const SkPoint* pts() const { return fCurrPt; }
2458 void next();
2459
2460private:
2461 int fCurrPtCount;
2462 const SkPoint* fCurrPt;
2463 const uint8_t* fCurrVerb;
2464 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002465 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002466 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002467 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002468};
2469
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002470ContourIter::ContourIter(const SkPathRef& pathRef) {
2471 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002472 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002473 fCurrPt = pathRef.points();
2474 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002475 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002476 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002477 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002478 this->next();
2479}
2480
2481void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002482 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002483 fDone = true;
2484 }
2485 if (fDone) {
2486 return;
2487 }
2488
2489 // skip pts of prev contour
2490 fCurrPt += fCurrPtCount;
2491
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002492 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002493 int ptCount = 1; // moveTo
2494 const uint8_t* verbs = fCurrVerb;
2495
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002496 for (--verbs; verbs > fStopVerbs; --verbs) {
2497 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002498 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002499 goto CONTOUR_END;
2500 case SkPath::kLine_Verb:
2501 ptCount += 1;
2502 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002503 case SkPath::kConic_Verb:
2504 fCurrConicWeight += 1;
2505 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002506 case SkPath::kQuad_Verb:
2507 ptCount += 2;
2508 break;
2509 case SkPath::kCubic_Verb:
2510 ptCount += 3;
2511 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002512 case SkPath::kClose_Verb:
2513 break;
2514 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002515 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002516 break;
2517 }
2518 }
2519CONTOUR_END:
2520 fCurrPtCount = ptCount;
2521 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002522 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002523}
2524
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002525// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002526static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002527 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2528 // We may get 0 when the above subtracts underflow. We expect this to be
2529 // very rare and lazily promote to double.
2530 if (0 == cross) {
2531 double p0x = SkScalarToDouble(p0.fX);
2532 double p0y = SkScalarToDouble(p0.fY);
2533
2534 double p1x = SkScalarToDouble(p1.fX);
2535 double p1y = SkScalarToDouble(p1.fY);
2536
2537 double p2x = SkScalarToDouble(p2.fX);
2538 double p2y = SkScalarToDouble(p2.fY);
2539
2540 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2541 (p1y - p0y) * (p2x - p0x));
2542
2543 }
2544 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002545}
2546
reed@google.comc1ea60a2012-01-31 15:15:36 +00002547// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002548static int find_max_y(const SkPoint pts[], int count) {
2549 SkASSERT(count > 0);
2550 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002551 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002552 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002553 SkScalar y = pts[i].fY;
2554 if (y > max) {
2555 max = y;
2556 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002557 }
2558 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002559 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002560}
2561
reed@google.comcabaf1d2012-01-11 21:03:05 +00002562static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2563 int i = index;
2564 for (;;) {
2565 i = (i + inc) % n;
2566 if (i == index) { // we wrapped around, so abort
2567 break;
2568 }
2569 if (pts[index] != pts[i]) { // found a different point, success!
2570 break;
2571 }
2572 }
2573 return i;
2574}
2575
reed@google.comc1ea60a2012-01-31 15:15:36 +00002576/**
2577 * Starting at index, and moving forward (incrementing), find the xmin and
2578 * xmax of the contiguous points that have the same Y.
2579 */
2580static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2581 int* maxIndexPtr) {
2582 const SkScalar y = pts[index].fY;
2583 SkScalar min = pts[index].fX;
2584 SkScalar max = min;
2585 int minIndex = index;
2586 int maxIndex = index;
2587 for (int i = index + 1; i < n; ++i) {
2588 if (pts[i].fY != y) {
2589 break;
2590 }
2591 SkScalar x = pts[i].fX;
2592 if (x < min) {
2593 min = x;
2594 minIndex = i;
2595 } else if (x > max) {
2596 max = x;
2597 maxIndex = i;
2598 }
2599 }
2600 *maxIndexPtr = maxIndex;
2601 return minIndex;
2602}
2603
reed026beb52015-06-10 14:23:15 -07002604static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2605 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002606}
2607
reed@google.comac8543f2012-01-30 20:51:25 +00002608/*
2609 * We loop through all contours, and keep the computed cross-product of the
2610 * contour that contained the global y-max. If we just look at the first
2611 * contour, we may find one that is wound the opposite way (correctly) since
2612 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2613 * that is outer most (or at least has the global y-max) before we can consider
2614 * its cross product.
2615 */
reed026beb52015-06-10 14:23:15 -07002616bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002617 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2618 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002619 return true;
2620 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002621
2622 // don't want to pay the cost for computing this if it
2623 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002624 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2625 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002626 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002627 return false;
2628 }
reed@google.com69a99432012-01-10 18:00:10 +00002629
reed026beb52015-06-10 14:23:15 -07002630 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002631
reed@google.comac8543f2012-01-30 20:51:25 +00002632 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002633 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002634 SkScalar ymaxCross = 0;
2635
reed@google.com69a99432012-01-10 18:00:10 +00002636 for (; !iter.done(); iter.next()) {
2637 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002638 if (n < 3) {
2639 continue;
2640 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002641
reed@google.comcabaf1d2012-01-11 21:03:05 +00002642 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002643 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002644 int index = find_max_y(pts, n);
2645 if (pts[index].fY < ymax) {
2646 continue;
2647 }
2648
2649 // If there is more than 1 distinct point at the y-max, we take the
2650 // x-min and x-max of them and just subtract to compute the dir.
2651 if (pts[(index + 1) % n].fY == pts[index].fY) {
2652 int maxIndex;
2653 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2654 if (minIndex == maxIndex) {
2655 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002656 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002657 SkASSERT(pts[minIndex].fY == pts[index].fY);
2658 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2659 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2660 // we just subtract the indices, and let that auto-convert to
2661 // SkScalar, since we just want - or + to signal the direction.
2662 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002663 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002664 TRY_CROSSPROD:
2665 // Find a next and prev index to use for the cross-product test,
2666 // but we try to find pts that form non-zero vectors from pts[index]
2667 //
2668 // Its possible that we can't find two non-degenerate vectors, so
2669 // we have to guard our search (e.g. all the pts could be in the
2670 // same place).
2671
2672 // we pass n - 1 instead of -1 so we don't foul up % operator by
2673 // passing it a negative LH argument.
2674 int prev = find_diff_pt(pts, index, n, n - 1);
2675 if (prev == index) {
2676 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002677 continue;
2678 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002679 int next = find_diff_pt(pts, index, n, 1);
2680 SkASSERT(next != index);
2681 cross = cross_prod(pts[prev], pts[index], pts[next]);
2682 // if we get a zero and the points are horizontal, then we look at the spread in
2683 // x-direction. We really should continue to walk away from the degeneracy until
2684 // there is a divergence.
2685 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2686 // construct the subtract so we get the correct Direction below
2687 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002688 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002689 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002690
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002691 if (cross) {
2692 // record our best guess so far
2693 ymax = pts[index].fY;
2694 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002695 }
2696 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002697 if (ymaxCross) {
2698 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002699 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002700 return true;
2701 } else {
2702 return false;
2703 }
reed@google.comac8543f2012-01-30 20:51:25 +00002704}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002705
2706///////////////////////////////////////////////////////////////////////////////
2707
caryclark9aacd902015-12-14 08:38:09 -08002708static bool between(SkScalar a, SkScalar b, SkScalar c) {
2709 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2710 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2711 return (a - b) * (c - b) <= 0;
2712}
2713
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002714static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2715 SkScalar D, SkScalar t) {
2716 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2717}
2718
2719static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2720 SkScalar t) {
2721 SkScalar A = c3 + 3*(c1 - c2) - c0;
2722 SkScalar B = 3*(c2 - c1 - c1 + c0);
2723 SkScalar C = 3*(c1 - c0);
2724 SkScalar D = c0;
2725 return eval_cubic_coeff(A, B, C, D, t);
2726}
2727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728template <size_t N> static void find_minmax(const SkPoint pts[],
2729 SkScalar* minPtr, SkScalar* maxPtr) {
2730 SkScalar min, max;
2731 min = max = pts[0].fX;
2732 for (size_t i = 1; i < N; ++i) {
2733 min = SkMinScalar(min, pts[i].fX);
2734 max = SkMaxScalar(max, pts[i].fX);
2735 }
2736 *minPtr = min;
2737 *maxPtr = max;
2738}
2739
caryclark9cb5d752015-12-18 04:35:24 -08002740static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2741 if (start.fY == end.fY) {
2742 return between(start.fX, x, end.fX) && x != end.fX;
2743 } else {
2744 return x == start.fX && y == start.fY;
2745 }
2746}
2747
caryclark9aacd902015-12-14 08:38:09 -08002748static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002749 SkScalar y0 = pts[0].fY;
2750 SkScalar y3 = pts[3].fY;
2751
2752 int dir = 1;
2753 if (y0 > y3) {
2754 SkTSwap(y0, y3);
2755 dir = -1;
2756 }
2757 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758 return 0;
2759 }
caryclark9cb5d752015-12-18 04:35:24 -08002760 if (checkOnCurve(x, y, pts[0], pts[3])) {
2761 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002762 return 0;
2763 }
caryclark9cb5d752015-12-18 04:35:24 -08002764 if (y == y3) {
2765 return 0;
2766 }
2767
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002768 // quickreject or quickaccept
2769 SkScalar min, max;
2770 find_minmax<4>(pts, &min, &max);
2771 if (x < min) {
2772 return 0;
2773 }
2774 if (x > max) {
2775 return dir;
2776 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002779 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002780 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002781 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002782 if (SkScalarNearlyEqual(xt, x)) {
2783 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2784 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002785 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002786 }
2787 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 return xt < x ? dir : 0;
2789}
2790
caryclark9aacd902015-12-14 08:38:09 -08002791static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792 SkPoint dst[10];
2793 int n = SkChopCubicAtYExtrema(pts, dst);
2794 int w = 0;
2795 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002796 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002797 }
2798 return w;
2799}
2800
caryclark9aacd902015-12-14 08:38:09 -08002801static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2802 SkASSERT(src);
2803 SkASSERT(t >= 0 && t <= 1);
2804 SkScalar src2w = src[2] * w;
2805 SkScalar C = src[0];
2806 SkScalar A = src[4] - 2 * src2w + C;
2807 SkScalar B = 2 * (src2w - C);
2808 return (A * t + B) * t + C;
2809}
2810
2811
2812static double conic_eval_denominator(SkScalar w, SkScalar t) {
2813 SkScalar B = 2 * (w - 1);
2814 SkScalar C = 1;
2815 SkScalar A = -B;
2816 return (A * t + B) * t + C;
2817}
2818
2819static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2820 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002821 SkScalar y0 = pts[0].fY;
2822 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 int dir = 1;
2825 if (y0 > y2) {
2826 SkTSwap(y0, y2);
2827 dir = -1;
2828 }
caryclark9aacd902015-12-14 08:38:09 -08002829 if (y < y0 || y > y2) {
2830 return 0;
2831 }
caryclark9cb5d752015-12-18 04:35:24 -08002832 if (checkOnCurve(x, y, pts[0], pts[2])) {
2833 *onCurveCount += 1;
2834 return 0;
2835 }
caryclark9aacd902015-12-14 08:38:09 -08002836 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002837 return 0;
2838 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002839
caryclark9aacd902015-12-14 08:38:09 -08002840 SkScalar roots[2];
2841 SkScalar A = pts[2].fY;
2842 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2843 SkScalar C = pts[0].fY;
2844 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2845 B -= C; // B = b*w - w * yCept + yCept - a
2846 C -= y;
2847 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2848 SkASSERT(n <= 1);
2849 SkScalar xt;
2850 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002851 // zero roots are returned only when y0 == y
2852 // Need [0] if dir == 1
2853 // and [2] if dir == -1
2854 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002855 } else {
2856 SkScalar t = roots[0];
2857 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2858 }
2859 if (SkScalarNearlyEqual(xt, x)) {
2860 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2861 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002862 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002863 }
2864 }
2865 return xt < x ? dir : 0;
2866}
2867
2868static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2869 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2870 if (y0 == y1) {
2871 return true;
2872 }
2873 if (y0 < y1) {
2874 return y1 <= y2;
2875 } else {
2876 return y1 >= y2;
2877 }
2878}
2879
2880static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2881 int* onCurveCount) {
2882 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002883 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002884 // If the data points are very large, the conic may not be monotonic but may also
2885 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002886 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2887 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2888 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002889 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2890 }
2891 return w;
2892}
2893
2894static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2895 SkScalar y0 = pts[0].fY;
2896 SkScalar y2 = pts[2].fY;
2897
2898 int dir = 1;
2899 if (y0 > y2) {
2900 SkTSwap(y0, y2);
2901 dir = -1;
2902 }
2903 if (y < y0 || y > y2) {
2904 return 0;
2905 }
caryclark9cb5d752015-12-18 04:35:24 -08002906 if (checkOnCurve(x, y, pts[0], pts[2])) {
2907 *onCurveCount += 1;
2908 return 0;
2909 }
caryclark9aacd902015-12-14 08:38:09 -08002910 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002911 return 0;
2912 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002913 // bounds check on X (not required. is it faster?)
2914#if 0
2915 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2916 return 0;
2917 }
2918#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002919
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 SkScalar roots[2];
2921 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2922 2 * (pts[1].fY - pts[0].fY),
2923 pts[0].fY - y,
2924 roots);
2925 SkASSERT(n <= 1);
2926 SkScalar xt;
2927 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002928 // zero roots are returned only when y0 == y
2929 // Need [0] if dir == 1
2930 // and [2] if dir == -1
2931 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002932 } else {
2933 SkScalar t = roots[0];
2934 SkScalar C = pts[0].fX;
2935 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2936 SkScalar B = 2 * (pts[1].fX - C);
2937 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2938 }
caryclark9aacd902015-12-14 08:38:09 -08002939 if (SkScalarNearlyEqual(xt, x)) {
2940 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2941 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002942 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002943 }
2944 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002945 return xt < x ? dir : 0;
2946}
2947
caryclark9aacd902015-12-14 08:38:09 -08002948static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002949 SkPoint dst[5];
2950 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002951
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2953 n = SkChopQuadAtYExtrema(pts, dst);
2954 pts = dst;
2955 }
caryclark9aacd902015-12-14 08:38:09 -08002956 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002957 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002958 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002959 }
2960 return w;
2961}
2962
caryclark9aacd902015-12-14 08:38:09 -08002963static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002964 SkScalar x0 = pts[0].fX;
2965 SkScalar y0 = pts[0].fY;
2966 SkScalar x1 = pts[1].fX;
2967 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002968
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002969 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002970
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 int dir = 1;
2972 if (y0 > y1) {
2973 SkTSwap(y0, y1);
2974 dir = -1;
2975 }
caryclark9aacd902015-12-14 08:38:09 -08002976 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 return 0;
2978 }
caryclark9cb5d752015-12-18 04:35:24 -08002979 if (checkOnCurve(x, y, pts[0], pts[1])) {
2980 *onCurveCount += 1;
2981 return 0;
2982 }
2983 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002984 return 0;
2985 }
2986 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002987
caryclark9aacd902015-12-14 08:38:09 -08002988 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002989 // zero cross means the point is on the line, and since the case where
2990 // y of the query point is at the end point is handled above, we can be
2991 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002992 if (x != x1 || y != pts[1].fY) {
2993 *onCurveCount += 1;
2994 }
caryclark9aacd902015-12-14 08:38:09 -08002995 dir = 0;
2996 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 dir = 0;
2998 }
2999 return dir;
3000}
3001
caryclark9aacd902015-12-14 08:38:09 -08003002static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3003 SkTDArray<SkVector>* tangents) {
3004 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3005 && !between(pts[2].fY, y, pts[3].fY)) {
3006 return;
3007 }
3008 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3009 && !between(pts[2].fX, x, pts[3].fX)) {
3010 return;
3011 }
3012 SkPoint dst[10];
3013 int n = SkChopCubicAtYExtrema(pts, dst);
3014 for (int i = 0; i <= n; ++i) {
3015 SkPoint* c = &dst[i * 3];
3016 SkScalar t;
3017 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
3018 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3019 if (!SkScalarNearlyEqual(x, xt)) {
3020 continue;
3021 }
3022 SkVector tangent;
3023 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3024 tangents->push(tangent);
3025 }
3026}
3027
3028static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3029 SkTDArray<SkVector>* tangents) {
3030 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3031 return;
3032 }
3033 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3034 return;
3035 }
3036 SkScalar roots[2];
3037 SkScalar A = pts[2].fY;
3038 SkScalar B = pts[1].fY * w - y * w + y;
3039 SkScalar C = pts[0].fY;
3040 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3041 B -= C; // B = b*w - w * yCept + yCept - a
3042 C -= y;
3043 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3044 for (int index = 0; index < n; ++index) {
3045 SkScalar t = roots[index];
3046 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3047 if (!SkScalarNearlyEqual(x, xt)) {
3048 continue;
3049 }
3050 SkConic conic(pts, w);
3051 tangents->push(conic.evalTangentAt(t));
3052 }
3053}
3054
3055static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3056 SkTDArray<SkVector>* tangents) {
3057 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3058 return;
3059 }
3060 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3061 return;
3062 }
3063 SkScalar roots[2];
3064 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3065 2 * (pts[1].fY - pts[0].fY),
3066 pts[0].fY - y,
3067 roots);
3068 for (int index = 0; index < n; ++index) {
3069 SkScalar t = roots[index];
3070 SkScalar C = pts[0].fX;
3071 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3072 SkScalar B = 2 * (pts[1].fX - C);
3073 SkScalar xt = (A * t + B) * t + C;
3074 if (!SkScalarNearlyEqual(x, xt)) {
3075 continue;
3076 }
3077 tangents->push(SkEvalQuadTangentAt(pts, t));
3078 }
3079}
3080
3081static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3082 SkTDArray<SkVector>* tangents) {
3083 SkScalar y0 = pts[0].fY;
3084 SkScalar y1 = pts[1].fY;
3085 if (!between(y0, y, y1)) {
3086 return;
3087 }
3088 SkScalar x0 = pts[0].fX;
3089 SkScalar x1 = pts[1].fX;
3090 if (!between(x0, x, x1)) {
3091 return;
3092 }
3093 SkScalar dx = x1 - x0;
3094 SkScalar dy = y1 - y0;
3095 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3096 return;
3097 }
3098 SkVector v;
3099 v.set(dx, dy);
3100 tangents->push(v);
3101}
3102
reed@google.com4db592c2013-10-30 17:39:43 +00003103static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3104 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3105}
3106
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003107bool SkPath::contains(SkScalar x, SkScalar y) const {
3108 bool isInverse = this->isInverseFillType();
3109 if (this->isEmpty()) {
3110 return isInverse;
3111 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003112
reed@google.com4db592c2013-10-30 17:39:43 +00003113 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003114 return isInverse;
3115 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003116
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003117 SkPath::Iter iter(*this, true);
3118 bool done = false;
3119 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003120 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003121 do {
3122 SkPoint pts[4];
3123 switch (iter.next(pts, false)) {
3124 case SkPath::kMove_Verb:
3125 case SkPath::kClose_Verb:
3126 break;
3127 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003128 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003129 break;
3130 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003131 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003132 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003133 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003134 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003135 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003136 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003137 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003138 break;
3139 case SkPath::kDone_Verb:
3140 done = true;
3141 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003142 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003143 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003144 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3145 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3146 if (evenOddFill) {
3147 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003148 }
caryclark9aacd902015-12-14 08:38:09 -08003149 if (w) {
3150 return !isInverse;
3151 }
3152 if (onCurveCount <= 1) {
3153 return SkToBool(onCurveCount) ^ isInverse;
3154 }
3155 if ((onCurveCount & 1) || evenOddFill) {
3156 return SkToBool(onCurveCount & 1) ^ isInverse;
3157 }
3158 // If the point touches an even number of curves, and the fill is winding, check for
3159 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3160 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003161 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003162 SkTDArray<SkVector> tangents;
3163 do {
3164 SkPoint pts[4];
3165 int oldCount = tangents.count();
3166 switch (iter.next(pts, false)) {
3167 case SkPath::kMove_Verb:
3168 case SkPath::kClose_Verb:
3169 break;
3170 case SkPath::kLine_Verb:
3171 tangent_line(pts, x, y, &tangents);
3172 break;
3173 case SkPath::kQuad_Verb:
3174 tangent_quad(pts, x, y, &tangents);
3175 break;
3176 case SkPath::kConic_Verb:
3177 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3178 break;
3179 case SkPath::kCubic_Verb:
3180 tangent_cubic(pts, x, y, &tangents);
3181 break;
3182 case SkPath::kDone_Verb:
3183 done = true;
3184 break;
3185 }
3186 if (tangents.count() > oldCount) {
3187 int last = tangents.count() - 1;
3188 const SkVector& tangent = tangents[last];
3189 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3190 tangents.remove(last);
3191 } else {
3192 for (int index = 0; index < last; ++index) {
3193 const SkVector& test = tangents[index];
3194 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003195 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3196 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003197 tangents.remove(last);
3198 tangents.removeShuffle(index);
3199 break;
3200 }
3201 }
3202 }
3203 }
3204 } while (!done);
3205 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003206}
fmalitaaa0df4e2015-12-01 09:13:23 -08003207
3208int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3209 SkScalar w, SkPoint pts[], int pow2) {
3210 const SkConic conic(p0, p1, p2, w);
3211 return conic.chopIntoQuadsPOW2(pts, pow2);
3212}