blob: dcc3e0202dc0cf8e0c1e88532b290f7a07930a63 [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);
djsollenf2b340f2016-01-29 08:51:04 -0800278 SkASSERT_RELEASE(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) {
caryclarkf1d41512016-02-09 10:30:22 -08001268 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001269 SkPoint srcPts[2];
1270 this->getLastPt(&srcPts[0]);
1271 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1272 // joining the endpoints.
1273 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1274 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001275 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001276 return;
1277 }
1278 // If the current point and target point for the arc are identical, it should be treated as a
1279 // zero length path. This ensures continuity in animations.
1280 srcPts[1].set(x, y);
1281 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001282 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001283 return;
1284 }
1285 rx = SkScalarAbs(rx);
1286 ry = SkScalarAbs(ry);
1287 SkVector midPointDistance = srcPts[0] - srcPts[1];
1288 midPointDistance *= 0.5f;
1289
1290 SkMatrix pointTransform;
1291 pointTransform.setRotate(-angle);
1292
1293 SkPoint transformedMidPoint;
1294 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1295 SkScalar squareRx = rx * rx;
1296 SkScalar squareRy = ry * ry;
1297 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1298 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1299
1300 // Check if the radii are big enough to draw the arc, scale radii if not.
1301 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1302 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1303 if (radiiScale > 1) {
1304 radiiScale = SkScalarSqrt(radiiScale);
1305 rx *= radiiScale;
1306 ry *= radiiScale;
1307 }
1308
1309 pointTransform.setScale(1 / rx, 1 / ry);
1310 pointTransform.preRotate(-angle);
1311
1312 SkPoint unitPts[2];
1313 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1314 SkVector delta = unitPts[1] - unitPts[0];
1315
1316 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1317 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1318
1319 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1320 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1321 scaleFactor = -scaleFactor;
1322 }
1323 delta.scale(scaleFactor);
1324 SkPoint centerPoint = unitPts[0] + unitPts[1];
1325 centerPoint *= 0.5f;
1326 centerPoint.offset(-delta.fY, delta.fX);
1327 unitPts[0] -= centerPoint;
1328 unitPts[1] -= centerPoint;
1329 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1330 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1331 SkScalar thetaArc = theta2 - theta1;
1332 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1333 thetaArc += SK_ScalarPI * 2;
1334 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1335 thetaArc -= SK_ScalarPI * 2;
1336 }
1337 pointTransform.setRotate(angle);
1338 pointTransform.preScale(rx, ry);
1339
1340 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1341 SkScalar thetaWidth = thetaArc / segments;
1342 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1343 if (!SkScalarIsFinite(t)) {
1344 return;
1345 }
1346 SkScalar startTheta = theta1;
1347 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1348 for (int i = 0; i < segments; ++i) {
1349 SkScalar endTheta = startTheta + thetaWidth;
1350 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1351
1352 unitPts[1].set(cosEndTheta, sinEndTheta);
1353 unitPts[1] += centerPoint;
1354 unitPts[0] = unitPts[1];
1355 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1356 SkPoint mapped[2];
1357 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1358 this->conicTo(mapped[0], mapped[1], w);
1359 startTheta = endTheta;
1360 }
1361}
1362
1363void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1364 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1365 SkPoint currentPoint;
1366 this->getLastPt(&currentPoint);
1367 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1368}
1369
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001370void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371 if (oval.isEmpty() || 0 == sweepAngle) {
1372 return;
1373 }
1374
1375 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1376
1377 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1378 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001379 } else {
1380 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382}
1383
1384/*
1385 Need to handle the case when the angle is sharp, and our computed end-points
1386 for the arc go behind pt1 and/or p2...
1387*/
reedc7789042015-01-29 12:59:11 -08001388void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001389 if (radius == 0) {
1390 this->lineTo(x1, y1);
1391 return;
1392 }
1393
1394 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001395
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 // need to know our prev pt so we can construct tangent vectors
1397 {
1398 SkPoint start;
1399 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001400 // Handle degenerate cases by adding a line to the first point and
1401 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 before.setNormalize(x1 - start.fX, y1 - start.fY);
1403 after.setNormalize(x2 - x1, y2 - y1);
1404 }
reed@google.comabf15c12011-01-18 20:35:51 +00001405
reed@android.com8a1c16f2008-12-17 15:59:43 +00001406 SkScalar cosh = SkPoint::DotProduct(before, after);
1407 SkScalar sinh = SkPoint::CrossProduct(before, after);
1408
1409 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001410 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 return;
1412 }
reed@google.comabf15c12011-01-18 20:35:51 +00001413
caryclark88651ae2016-01-20 11:55:11 -08001414 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001415
1416 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1417 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001418 after.setLength(dist);
1419 this->lineTo(xx, yy);
1420 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1421 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422}
1423
1424///////////////////////////////////////////////////////////////////////////////
1425
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001426void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001427 SkMatrix matrix;
1428
1429 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001430 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431}
1432
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001433void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001434 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001436 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 SkPoint pts[4];
1438 Verb verb;
1439
1440 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001441 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 while ((verb = iter.next(pts)) != kDone_Verb) {
1443 switch (verb) {
1444 case kMove_Verb:
1445 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001446 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1447 injectMoveToIfNeeded(); // In case last contour is closed
1448 this->lineTo(pts[0]);
1449 } else {
1450 this->moveTo(pts[0]);
1451 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452 break;
1453 case kLine_Verb:
1454 proc(matrix, &pts[1], &pts[1], 1);
1455 this->lineTo(pts[1]);
1456 break;
1457 case kQuad_Verb:
1458 proc(matrix, &pts[1], &pts[1], 2);
1459 this->quadTo(pts[1], pts[2]);
1460 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001461 case kConic_Verb:
1462 proc(matrix, &pts[1], &pts[1], 2);
1463 this->conicTo(pts[1], pts[2], iter.conicWeight());
1464 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 case kCubic_Verb:
1466 proc(matrix, &pts[1], &pts[1], 3);
1467 this->cubicTo(pts[1], pts[2], pts[3]);
1468 break;
1469 case kClose_Verb:
1470 this->close();
1471 break;
1472 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001473 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001475 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 }
1477}
1478
1479///////////////////////////////////////////////////////////////////////////////
1480
reed@google.com277c3f82013-05-31 15:17:50 +00001481static int pts_in_verb(unsigned verb) {
1482 static const uint8_t gPtsInVerb[] = {
1483 1, // kMove
1484 1, // kLine
1485 2, // kQuad
1486 2, // kConic
1487 3, // kCubic
1488 0, // kClose
1489 0 // kDone
1490 };
1491
1492 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1493 return gPtsInVerb[verb];
1494}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496// ignore the last point of the 1st contour
1497void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001498 int i, vcount = path.fPathRef->countVerbs();
1499 // exit early if the path is empty, or just has a moveTo.
1500 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 return;
1502 }
1503
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001504 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001506 const uint8_t* verbs = path.fPathRef->verbs();
1507 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001508 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001510 SkASSERT(verbs[~0] == kMove_Verb);
1511 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001512 unsigned v = verbs[~i];
1513 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 if (n == 0) {
1515 break;
1516 }
1517 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001518 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 }
1520
1521 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001522 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 case kLine_Verb:
1524 this->lineTo(pts[-1].fX, pts[-1].fY);
1525 break;
1526 case kQuad_Verb:
1527 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1528 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001529 case kConic_Verb:
1530 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1531 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 case kCubic_Verb:
1533 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1534 pts[-3].fX, pts[-3].fY);
1535 break;
1536 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001537 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538 break;
1539 }
reed@google.com277c3f82013-05-31 15:17:50 +00001540 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 }
1542}
1543
reed@google.com63d73742012-01-10 15:33:12 +00001544void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001545 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001546
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 const SkPoint* pts = src.fPathRef->pointsEnd();
1548 // we will iterator through src's verbs backwards
1549 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1550 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001551 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001552
1553 bool needMove = true;
1554 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001555 while (verbs < verbsEnd) {
1556 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001557 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001558
1559 if (needMove) {
1560 --pts;
1561 this->moveTo(pts->fX, pts->fY);
1562 needMove = false;
1563 }
1564 pts -= n;
1565 switch (v) {
1566 case kMove_Verb:
1567 if (needClose) {
1568 this->close();
1569 needClose = false;
1570 }
1571 needMove = true;
1572 pts += 1; // so we see the point in "if (needMove)" above
1573 break;
1574 case kLine_Verb:
1575 this->lineTo(pts[0]);
1576 break;
1577 case kQuad_Verb:
1578 this->quadTo(pts[1], pts[0]);
1579 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001580 case kConic_Verb:
1581 this->conicTo(pts[1], pts[0], *--conicWeights);
1582 break;
reed@google.com63d73742012-01-10 15:33:12 +00001583 case kCubic_Verb:
1584 this->cubicTo(pts[2], pts[1], pts[0]);
1585 break;
1586 case kClose_Verb:
1587 needClose = true;
1588 break;
1589 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001590 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001591 }
1592 }
1593}
1594
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595///////////////////////////////////////////////////////////////////////////////
1596
1597void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1598 SkMatrix matrix;
1599
1600 matrix.setTranslate(dx, dy);
1601 this->transform(matrix, dst);
1602}
1603
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1605 int level = 2) {
1606 if (--level >= 0) {
1607 SkPoint tmp[7];
1608
1609 SkChopCubicAtHalf(pts, tmp);
1610 subdivide_cubic_to(path, &tmp[0], level);
1611 subdivide_cubic_to(path, &tmp[3], level);
1612 } else {
1613 path->cubicTo(pts[1], pts[2], pts[3]);
1614 }
1615}
1616
1617void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1618 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001619 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620 dst = (SkPath*)this;
1621 }
1622
tomhudson@google.com8d430182011-06-06 19:11:19 +00001623 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624 SkPath tmp;
1625 tmp.fFillType = fFillType;
1626
1627 SkPath::Iter iter(*this, false);
1628 SkPoint pts[4];
1629 SkPath::Verb verb;
1630
reed@google.com4a3b7142012-05-16 17:16:46 +00001631 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 switch (verb) {
1633 case kMove_Verb:
1634 tmp.moveTo(pts[0]);
1635 break;
1636 case kLine_Verb:
1637 tmp.lineTo(pts[1]);
1638 break;
1639 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001640 // promote the quad to a conic
1641 tmp.conicTo(pts[1], pts[2],
1642 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001644 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001645 tmp.conicTo(pts[1], pts[2],
1646 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001647 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 case kCubic_Verb:
1649 subdivide_cubic_to(&tmp, pts);
1650 break;
1651 case kClose_Verb:
1652 tmp.close();
1653 break;
1654 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001655 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656 break;
1657 }
1658 }
1659
1660 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001661 SkPathRef::Editor ed(&dst->fPathRef);
1662 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001663 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001665 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1666
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001669 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001670 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001672
reed026beb52015-06-10 14:23:15 -07001673 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1674 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001675 } else {
1676 SkScalar det2x2 =
1677 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1678 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1679 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001680 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1681 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001682 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001683 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001684 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001685 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001686 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001687 }
1688 }
1689
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 SkDEBUGCODE(dst->validate();)
1691 }
1692}
1693
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694///////////////////////////////////////////////////////////////////////////////
1695///////////////////////////////////////////////////////////////////////////////
1696
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001697enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001698 kEmptyContour_SegmentState, // The current contour is empty. We may be
1699 // starting processing or we may have just
1700 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001701 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1702 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1703 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704};
1705
1706SkPath::Iter::Iter() {
1707#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001708 fPts = nullptr;
1709 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001711 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001712 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713#endif
1714 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001715 fVerbs = nullptr;
1716 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717 fNeedClose = false;
1718}
1719
1720SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1721 this->setPath(path, forceClose);
1722}
1723
1724void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001725 fPts = path.fPathRef->points();
1726 fVerbs = path.fPathRef->verbs();
1727 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001728 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001729 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001730 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 fForceClose = SkToU8(forceClose);
1732 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001733 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734}
1735
1736bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001737 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 return false;
1739 }
1740 if (fForceClose) {
1741 return true;
1742 }
1743
1744 const uint8_t* verbs = fVerbs;
1745 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001747 if (kMove_Verb == *(verbs - 1)) {
1748 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 }
1750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001751 while (verbs > stop) {
1752 // verbs points one beyond the current verb, decrement first.
1753 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754 if (kMove_Verb == v) {
1755 break;
1756 }
1757 if (kClose_Verb == v) {
1758 return true;
1759 }
1760 }
1761 return false;
1762}
1763
1764SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001765 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001767 // A special case: if both points are NaN, SkPoint::operation== returns
1768 // false, but the iterator expects that they are treated as the same.
1769 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001770 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1771 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001772 return kClose_Verb;
1773 }
1774
reed@google.com9e25dbf2012-05-15 17:05:38 +00001775 pts[0] = fLastPt;
1776 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 fLastPt = fMoveTo;
1778 fCloseLine = true;
1779 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001780 } else {
1781 pts[0] = fMoveTo;
1782 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784}
1785
reed@google.com9e25dbf2012-05-15 17:05:38 +00001786const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001787 if (fSegmentState == kAfterMove_SegmentState) {
1788 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001789 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1793 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001794 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796}
1797
caryclarke8c56662015-07-14 11:19:26 -07001798void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001799 // We need to step over anything that will not move the current draw point
1800 // forward before the next move is seen
1801 const uint8_t* lastMoveVerb = 0;
1802 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001803 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 SkPoint lastPt = fLastPt;
1805 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001806 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807 switch (verb) {
1808 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 // Keep a record of this most recent move
1810 lastMoveVerb = fVerbs;
1811 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001812 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001813 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001814 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 fPts++;
1816 break;
1817
1818 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001819 // A close when we are in a segment is always valid except when it
1820 // follows a move which follows a segment.
1821 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 return;
1823 }
1824 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001825 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 break;
1827
1828 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001829 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 if (lastMoveVerb) {
1831 fVerbs = lastMoveVerb;
1832 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001833 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 return;
1835 }
1836 return;
1837 }
1838 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001839 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001840 fPts++;
1841 break;
1842
reed@google.com277c3f82013-05-31 15:17:50 +00001843 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001845 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 if (lastMoveVerb) {
1847 fVerbs = lastMoveVerb;
1848 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001849 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 return;
1851 }
1852 return;
1853 }
1854 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001857 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 break;
1859
1860 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001861 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 if (lastMoveVerb) {
1863 fVerbs = lastMoveVerb;
1864 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001865 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 return;
1867 }
1868 return;
1869 }
1870 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001871 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 fPts += 3;
1873 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001874
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001876 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 }
1878 }
1879}
1880
reed@google.com4a3b7142012-05-16 17:16:46 +00001881SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001882 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 // Close the curve if requested and if there is some curve to close
1886 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001887 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 return kLine_Verb;
1889 }
1890 fNeedClose = false;
1891 return kClose_Verb;
1892 }
1893 return kDone_Verb;
1894 }
1895
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001896 // fVerbs is one beyond the current verb, decrement first
1897 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001898 const SkPoint* SK_RESTRICT srcPts = fPts;
1899 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900
1901 switch (verb) {
1902 case kMove_Verb:
1903 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 verb = this->autoClose(pts);
1906 if (verb == kClose_Verb) {
1907 fNeedClose = false;
1908 }
1909 return (Verb)verb;
1910 }
1911 if (fVerbs == fVerbStop) { // might be a trailing moveto
1912 return kDone_Verb;
1913 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001914 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001917 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 fNeedClose = fForceClose;
1920 break;
1921 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001922 pts[0] = this->cons_moveTo();
1923 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001924 fLastPt = srcPts[0];
1925 fCloseLine = false;
1926 srcPts += 1;
1927 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001928 case kConic_Verb:
1929 fConicWeights += 1;
1930 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001932 pts[0] = this->cons_moveTo();
1933 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 fLastPt = srcPts[1];
1935 srcPts += 2;
1936 break;
1937 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001938 pts[0] = this->cons_moveTo();
1939 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001940 fLastPt = srcPts[2];
1941 srcPts += 3;
1942 break;
1943 case kClose_Verb:
1944 verb = this->autoClose(pts);
1945 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001946 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001947 } else {
1948 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001949 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 break;
1953 }
1954 fPts = srcPts;
1955 return (Verb)verb;
1956}
1957
1958///////////////////////////////////////////////////////////////////////////////
1959
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001961 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962*/
1963
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001964size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 SkDEBUGCODE(this->validate();)
1966
halcanary96fcdcc2015-08-27 07:41:13 -07001967 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001968 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001969 return SkAlign4(byteCount);
1970 }
1971
1972 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001973
robertphillips@google.com466310d2013-12-03 16:43:54 +00001974 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001975 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001976 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001977 (fIsVolatile << kIsVolatile_SerializationShift) |
1978 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001979
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001980 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001981
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001982 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001983
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001984 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001985 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986}
1987
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001988size_t SkPath::readFromMemory(const void* storage, size_t length) {
1989 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001990
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001991 int32_t packed;
1992 if (!buffer.readS32(&packed)) {
1993 return 0;
1994 }
1995
reed8f086022015-06-11 14:22:19 -07001996 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001997
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001998 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1999 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002000 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002001 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002002 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002003 if (!pathRef) {
2004 return 0;
2005 }
2006
2007 fPathRef.reset(pathRef);
2008 SkDEBUGCODE(this->validate();)
2009 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002010
reed8f086022015-06-11 14:22:19 -07002011 // compatibility check
2012 if (version < kPathPrivFirstDirection_Version) {
2013 switch (dir) { // old values
2014 case 0:
2015 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2016 break;
2017 case 1:
2018 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2019 break;
2020 case 2:
2021 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2022 break;
2023 default:
2024 SkASSERT(false);
2025 }
2026 } else {
2027 fFirstDirection = dir;
2028 }
2029
ajumaf8aec582016-01-13 13:46:31 -08002030 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031}
2032
2033///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034
reede05fed02014-12-15 07:59:53 -08002035#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002036#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002037
reed@google.com51bbe752013-01-17 22:07:50 +00002038static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002039 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002040 str->append(label);
2041 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002042
reed@google.com51bbe752013-01-17 22:07:50 +00002043 const SkScalar* values = &pts[0].fX;
2044 count *= 2;
2045
2046 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002047 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002048 if (i < count - 1) {
2049 str->append(", ");
2050 }
2051 }
reed@google.com277c3f82013-05-31 15:17:50 +00002052 if (conicWeight >= 0) {
2053 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002054 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002055 }
caryclark08fa28c2014-10-23 13:08:56 -07002056 str->append(");");
reede05fed02014-12-15 07:59:53 -08002057 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002058 str->append(" // ");
2059 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002060 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002061 if (i < count - 1) {
2062 str->append(", ");
2063 }
2064 }
2065 if (conicWeight >= 0) {
2066 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002067 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002068 }
2069 }
2070 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002071}
2072
caryclarke9562592014-09-15 09:26:09 -07002073void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002074 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002075 Iter iter(*this, forceClose);
2076 SkPoint pts[4];
2077 Verb verb;
2078
caryclark66a5d8b2014-06-24 08:30:15 -07002079 if (!wStream) {
2080 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2081 }
reed@google.com51bbe752013-01-17 22:07:50 +00002082 SkString builder;
2083
reed@google.com4a3b7142012-05-16 17:16:46 +00002084 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002085 switch (verb) {
2086 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002087 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 break;
2089 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002090 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002091 break;
2092 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002093 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002095 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002096 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002097 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002099 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 break;
2101 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002102 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 break;
2104 default:
2105 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2106 verb = kDone_Verb; // stop the loop
2107 break;
2108 }
caryclark1049f122015-04-20 08:31:59 -07002109 if (!wStream && builder.size()) {
2110 SkDebugf("%s", builder.c_str());
2111 builder.reset();
2112 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113 }
caryclark66a5d8b2014-06-24 08:30:15 -07002114 if (wStream) {
2115 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002116 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117}
2118
reed@android.come522ca52009-11-23 20:10:41 +00002119void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002120 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002121}
2122
2123void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002124 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002125}
2126
2127#ifdef SK_DEBUG
2128void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002129 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002130
djsollen@google.com077348c2012-10-22 20:23:32 +00002131#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002132 if (!fBoundsIsDirty) {
2133 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002134
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002135 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002136 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002137
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002138 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002139 // if we're empty, fBounds may be empty but translated, so we can't
2140 // necessarily compare to bounds directly
2141 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2142 // be [2, 2, 2, 2]
2143 SkASSERT(bounds.isEmpty());
2144 SkASSERT(fBounds.isEmpty());
2145 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002146 if (bounds.isEmpty()) {
2147 SkASSERT(fBounds.isEmpty());
2148 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002149 if (!fBounds.isEmpty()) {
2150 SkASSERT(fBounds.contains(bounds));
2151 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002152 }
reed@android.come522ca52009-11-23 20:10:41 +00002153 }
2154 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002155#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002156}
djsollen@google.com077348c2012-10-22 20:23:32 +00002157#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002158
reed@google.com04863fa2011-05-15 04:08:24 +00002159///////////////////////////////////////////////////////////////////////////////
2160
reed@google.com0b7b9822011-05-16 12:29:27 +00002161static int sign(SkScalar x) { return x < 0; }
2162#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002163
robertphillipsc506e302014-09-16 09:43:31 -07002164enum DirChange {
2165 kLeft_DirChange,
2166 kRight_DirChange,
2167 kStraight_DirChange,
2168 kBackwards_DirChange,
2169
2170 kInvalid_DirChange
2171};
2172
2173
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002174static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002175 // The error epsilon was empirically derived; worse case round rects
2176 // with a mid point outset by 2x float epsilon in tests had an error
2177 // of 12.
2178 const int epsilon = 16;
2179 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2180 return false;
2181 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002182 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002183 int aBits = SkFloatAs2sCompliment(compA);
2184 int bBits = SkFloatAs2sCompliment(compB);
2185 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002186}
2187
caryclarkb4216502015-03-02 13:02:34 -08002188static bool approximately_zero_when_compared_to(double x, double y) {
2189 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002190}
2191
caryclarkb4216502015-03-02 13:02:34 -08002192
reed@google.com04863fa2011-05-15 04:08:24 +00002193// only valid for a single contour
2194struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002195 Convexicator()
2196 : fPtCount(0)
2197 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002198 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002199 , fIsFinite(true)
2200 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002201 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002202 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002203 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002204 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002205 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002206 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002207
2208 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002209 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002210 }
2211
2212 SkPath::Convexity getConvexity() const { return fConvexity; }
2213
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002214 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002215 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002216
reed@google.com04863fa2011-05-15 04:08:24 +00002217 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002218 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002219 return;
2220 }
2221
2222 if (0 == fPtCount) {
2223 fCurrPt = pt;
2224 ++fPtCount;
2225 } else {
2226 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002227 SkScalar lengthSqd = vec.lengthSqd();
2228 if (!SkScalarIsFinite(lengthSqd)) {
2229 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002230 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002231 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002232 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002233 fCurrPt = pt;
2234 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002235 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002236 } else {
2237 SkASSERT(fPtCount > 2);
2238 this->addVec(vec);
2239 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002240
reed@google.com85b6e392011-05-15 20:25:17 +00002241 int sx = sign(vec.fX);
2242 int sy = sign(vec.fY);
2243 fDx += (sx != fSx);
2244 fDy += (sy != fSy);
2245 fSx = sx;
2246 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002247
reed@google.com85b6e392011-05-15 20:25:17 +00002248 if (fDx > 3 || fDy > 3) {
2249 fConvexity = SkPath::kConcave_Convexity;
2250 }
reed@google.com04863fa2011-05-15 04:08:24 +00002251 }
2252 }
2253 }
2254
2255 void close() {
2256 if (fPtCount > 2) {
2257 this->addVec(fFirstVec);
2258 }
2259 }
2260
caryclarkb4216502015-03-02 13:02:34 -08002261 DirChange directionChange(const SkVector& curVec) {
2262 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2263
2264 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2265 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2266 largest = SkTMax(largest, -smallest);
2267
2268 if (!almost_equal(largest, largest + cross)) {
2269 int sign = SkScalarSignAsInt(cross);
2270 if (sign) {
2271 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2272 }
2273 }
2274
2275 if (cross) {
2276 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2277 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2278 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2279 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2280 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2281 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2282 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2283 if (sign) {
2284 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2285 }
2286 }
2287 }
2288
2289 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2290 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2291 fLastVec.dot(curVec) < 0.0f) {
2292 return kBackwards_DirChange;
2293 }
2294
2295 return kStraight_DirChange;
2296 }
2297
2298
caryclarkd3d1a982014-12-08 04:57:38 -08002299 bool isFinite() const {
2300 return fIsFinite;
2301 }
2302
caryclark5ccef572015-03-02 10:07:56 -08002303 void setCurve(bool isCurve) {
2304 fIsCurve = isCurve;
2305 }
2306
reed@google.com04863fa2011-05-15 04:08:24 +00002307private:
2308 void addVec(const SkVector& vec) {
2309 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002310 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002311 switch (dir) {
2312 case kLeft_DirChange: // fall through
2313 case kRight_DirChange:
2314 if (kInvalid_DirChange == fExpectedDir) {
2315 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002316 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2317 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002318 } else if (dir != fExpectedDir) {
2319 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002320 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002321 }
2322 fLastVec = vec;
2323 break;
2324 case kStraight_DirChange:
2325 break;
2326 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002327 if (fIsCurve) {
2328 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002329 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002330 }
robertphillipsc506e302014-09-16 09:43:31 -07002331 fLastVec = vec;
2332 break;
2333 case kInvalid_DirChange:
2334 SkFAIL("Use of invalid direction change flag");
2335 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002336 }
2337 }
2338
caryclarkb4216502015-03-02 13:02:34 -08002339 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002340 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002341 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002342 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2343 // value with the current vec is deemed to be of a significant value.
2344 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002345 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002346 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002347 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002348 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002349 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002350 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002351 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002352};
2353
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002354SkPath::Convexity SkPath::internalGetConvexity() const {
2355 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002356 SkPoint pts[4];
2357 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002358 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002359
2360 int contourCount = 0;
2361 int count;
2362 Convexicator state;
2363
caryclarkd3d1a982014-12-08 04:57:38 -08002364 if (!isFinite()) {
2365 return kUnknown_Convexity;
2366 }
caryclarke8c56662015-07-14 11:19:26 -07002367 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002368 switch (verb) {
2369 case kMove_Verb:
2370 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002371 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002372 return kConcave_Convexity;
2373 }
2374 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002375 // fall through
2376 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002377 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002378 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002379 break;
caryclark5ccef572015-03-02 10:07:56 -08002380 case kQuad_Verb:
2381 // fall through
2382 case kConic_Verb:
2383 // fall through
2384 case kCubic_Verb:
2385 count = 2 + (kCubic_Verb == verb);
2386 // As an additional enhancement, this could set curve true only
2387 // if the curve is nonlinear
2388 state.setCurve(true);
2389 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002390 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002391 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002392 state.close();
2393 count = 0;
2394 break;
2395 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002396 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002397 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002398 return kConcave_Convexity;
2399 }
2400
2401 for (int i = 1; i <= count; i++) {
2402 state.addPt(pts[i]);
2403 }
2404 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002405 if (!state.isFinite()) {
2406 return kUnknown_Convexity;
2407 }
reed@google.com04863fa2011-05-15 04:08:24 +00002408 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002409 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 return kConcave_Convexity;
2411 }
2412 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002413 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002414 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2415 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002416 }
2417 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002418}
reed@google.com69a99432012-01-10 18:00:10 +00002419
2420///////////////////////////////////////////////////////////////////////////////
2421
2422class ContourIter {
2423public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002424 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002425
2426 bool done() const { return fDone; }
2427 // if !done() then these may be called
2428 int count() const { return fCurrPtCount; }
2429 const SkPoint* pts() const { return fCurrPt; }
2430 void next();
2431
2432private:
2433 int fCurrPtCount;
2434 const SkPoint* fCurrPt;
2435 const uint8_t* fCurrVerb;
2436 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002437 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002438 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002439 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002440};
2441
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002442ContourIter::ContourIter(const SkPathRef& pathRef) {
2443 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002444 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002445 fCurrPt = pathRef.points();
2446 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002447 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002448 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002449 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002450 this->next();
2451}
2452
2453void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002454 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002455 fDone = true;
2456 }
2457 if (fDone) {
2458 return;
2459 }
2460
2461 // skip pts of prev contour
2462 fCurrPt += fCurrPtCount;
2463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002464 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002465 int ptCount = 1; // moveTo
2466 const uint8_t* verbs = fCurrVerb;
2467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002468 for (--verbs; verbs > fStopVerbs; --verbs) {
2469 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002470 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002471 goto CONTOUR_END;
2472 case SkPath::kLine_Verb:
2473 ptCount += 1;
2474 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002475 case SkPath::kConic_Verb:
2476 fCurrConicWeight += 1;
2477 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002478 case SkPath::kQuad_Verb:
2479 ptCount += 2;
2480 break;
2481 case SkPath::kCubic_Verb:
2482 ptCount += 3;
2483 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002484 case SkPath::kClose_Verb:
2485 break;
2486 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002487 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002488 break;
2489 }
2490 }
2491CONTOUR_END:
2492 fCurrPtCount = ptCount;
2493 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002494 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002495}
2496
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002497// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002498static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002499 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2500 // We may get 0 when the above subtracts underflow. We expect this to be
2501 // very rare and lazily promote to double.
2502 if (0 == cross) {
2503 double p0x = SkScalarToDouble(p0.fX);
2504 double p0y = SkScalarToDouble(p0.fY);
2505
2506 double p1x = SkScalarToDouble(p1.fX);
2507 double p1y = SkScalarToDouble(p1.fY);
2508
2509 double p2x = SkScalarToDouble(p2.fX);
2510 double p2y = SkScalarToDouble(p2.fY);
2511
2512 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2513 (p1y - p0y) * (p2x - p0x));
2514
2515 }
2516 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002517}
2518
reed@google.comc1ea60a2012-01-31 15:15:36 +00002519// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002520static int find_max_y(const SkPoint pts[], int count) {
2521 SkASSERT(count > 0);
2522 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002523 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002524 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002525 SkScalar y = pts[i].fY;
2526 if (y > max) {
2527 max = y;
2528 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002529 }
2530 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002531 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002532}
2533
reed@google.comcabaf1d2012-01-11 21:03:05 +00002534static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2535 int i = index;
2536 for (;;) {
2537 i = (i + inc) % n;
2538 if (i == index) { // we wrapped around, so abort
2539 break;
2540 }
2541 if (pts[index] != pts[i]) { // found a different point, success!
2542 break;
2543 }
2544 }
2545 return i;
2546}
2547
reed@google.comc1ea60a2012-01-31 15:15:36 +00002548/**
2549 * Starting at index, and moving forward (incrementing), find the xmin and
2550 * xmax of the contiguous points that have the same Y.
2551 */
2552static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2553 int* maxIndexPtr) {
2554 const SkScalar y = pts[index].fY;
2555 SkScalar min = pts[index].fX;
2556 SkScalar max = min;
2557 int minIndex = index;
2558 int maxIndex = index;
2559 for (int i = index + 1; i < n; ++i) {
2560 if (pts[i].fY != y) {
2561 break;
2562 }
2563 SkScalar x = pts[i].fX;
2564 if (x < min) {
2565 min = x;
2566 minIndex = i;
2567 } else if (x > max) {
2568 max = x;
2569 maxIndex = i;
2570 }
2571 }
2572 *maxIndexPtr = maxIndex;
2573 return minIndex;
2574}
2575
reed026beb52015-06-10 14:23:15 -07002576static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2577 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002578}
2579
reed@google.comac8543f2012-01-30 20:51:25 +00002580/*
2581 * We loop through all contours, and keep the computed cross-product of the
2582 * contour that contained the global y-max. If we just look at the first
2583 * contour, we may find one that is wound the opposite way (correctly) since
2584 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2585 * that is outer most (or at least has the global y-max) before we can consider
2586 * its cross product.
2587 */
reed026beb52015-06-10 14:23:15 -07002588bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002589 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2590 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002591 return true;
2592 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002593
2594 // don't want to pay the cost for computing this if it
2595 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002596 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2597 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002598 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002599 return false;
2600 }
reed@google.com69a99432012-01-10 18:00:10 +00002601
reed026beb52015-06-10 14:23:15 -07002602 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002603
reed@google.comac8543f2012-01-30 20:51:25 +00002604 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002605 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002606 SkScalar ymaxCross = 0;
2607
reed@google.com69a99432012-01-10 18:00:10 +00002608 for (; !iter.done(); iter.next()) {
2609 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002610 if (n < 3) {
2611 continue;
2612 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002613
reed@google.comcabaf1d2012-01-11 21:03:05 +00002614 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002615 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002616 int index = find_max_y(pts, n);
2617 if (pts[index].fY < ymax) {
2618 continue;
2619 }
2620
2621 // If there is more than 1 distinct point at the y-max, we take the
2622 // x-min and x-max of them and just subtract to compute the dir.
2623 if (pts[(index + 1) % n].fY == pts[index].fY) {
2624 int maxIndex;
2625 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2626 if (minIndex == maxIndex) {
2627 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002628 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002629 SkASSERT(pts[minIndex].fY == pts[index].fY);
2630 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2631 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2632 // we just subtract the indices, and let that auto-convert to
2633 // SkScalar, since we just want - or + to signal the direction.
2634 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002635 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002636 TRY_CROSSPROD:
2637 // Find a next and prev index to use for the cross-product test,
2638 // but we try to find pts that form non-zero vectors from pts[index]
2639 //
2640 // Its possible that we can't find two non-degenerate vectors, so
2641 // we have to guard our search (e.g. all the pts could be in the
2642 // same place).
2643
2644 // we pass n - 1 instead of -1 so we don't foul up % operator by
2645 // passing it a negative LH argument.
2646 int prev = find_diff_pt(pts, index, n, n - 1);
2647 if (prev == index) {
2648 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002649 continue;
2650 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002651 int next = find_diff_pt(pts, index, n, 1);
2652 SkASSERT(next != index);
2653 cross = cross_prod(pts[prev], pts[index], pts[next]);
2654 // if we get a zero and the points are horizontal, then we look at the spread in
2655 // x-direction. We really should continue to walk away from the degeneracy until
2656 // there is a divergence.
2657 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2658 // construct the subtract so we get the correct Direction below
2659 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002660 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002661 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002662
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002663 if (cross) {
2664 // record our best guess so far
2665 ymax = pts[index].fY;
2666 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002667 }
2668 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002669 if (ymaxCross) {
2670 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002671 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002672 return true;
2673 } else {
2674 return false;
2675 }
reed@google.comac8543f2012-01-30 20:51:25 +00002676}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002677
2678///////////////////////////////////////////////////////////////////////////////
2679
caryclark9aacd902015-12-14 08:38:09 -08002680static bool between(SkScalar a, SkScalar b, SkScalar c) {
2681 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2682 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2683 return (a - b) * (c - b) <= 0;
2684}
2685
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002686static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2687 SkScalar D, SkScalar t) {
2688 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2689}
2690
2691static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2692 SkScalar t) {
2693 SkScalar A = c3 + 3*(c1 - c2) - c0;
2694 SkScalar B = 3*(c2 - c1 - c1 + c0);
2695 SkScalar C = 3*(c1 - c0);
2696 SkScalar D = c0;
2697 return eval_cubic_coeff(A, B, C, D, t);
2698}
2699
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002700template <size_t N> static void find_minmax(const SkPoint pts[],
2701 SkScalar* minPtr, SkScalar* maxPtr) {
2702 SkScalar min, max;
2703 min = max = pts[0].fX;
2704 for (size_t i = 1; i < N; ++i) {
2705 min = SkMinScalar(min, pts[i].fX);
2706 max = SkMaxScalar(max, pts[i].fX);
2707 }
2708 *minPtr = min;
2709 *maxPtr = max;
2710}
2711
caryclark9cb5d752015-12-18 04:35:24 -08002712static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2713 if (start.fY == end.fY) {
2714 return between(start.fX, x, end.fX) && x != end.fX;
2715 } else {
2716 return x == start.fX && y == start.fY;
2717 }
2718}
2719
caryclark9aacd902015-12-14 08:38:09 -08002720static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002721 SkScalar y0 = pts[0].fY;
2722 SkScalar y3 = pts[3].fY;
2723
2724 int dir = 1;
2725 if (y0 > y3) {
2726 SkTSwap(y0, y3);
2727 dir = -1;
2728 }
2729 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002730 return 0;
2731 }
caryclark9cb5d752015-12-18 04:35:24 -08002732 if (checkOnCurve(x, y, pts[0], pts[3])) {
2733 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002734 return 0;
2735 }
caryclark9cb5d752015-12-18 04:35:24 -08002736 if (y == y3) {
2737 return 0;
2738 }
2739
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002740 // quickreject or quickaccept
2741 SkScalar min, max;
2742 find_minmax<4>(pts, &min, &max);
2743 if (x < min) {
2744 return 0;
2745 }
2746 if (x > max) {
2747 return dir;
2748 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002749
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002750 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002751 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002752 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002753 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002754 if (SkScalarNearlyEqual(xt, x)) {
2755 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2756 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002757 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002758 }
2759 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760 return xt < x ? dir : 0;
2761}
2762
caryclark9aacd902015-12-14 08:38:09 -08002763static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002764 SkPoint dst[10];
2765 int n = SkChopCubicAtYExtrema(pts, dst);
2766 int w = 0;
2767 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002768 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769 }
2770 return w;
2771}
2772
caryclark9aacd902015-12-14 08:38:09 -08002773static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2774 SkASSERT(src);
2775 SkASSERT(t >= 0 && t <= 1);
2776 SkScalar src2w = src[2] * w;
2777 SkScalar C = src[0];
2778 SkScalar A = src[4] - 2 * src2w + C;
2779 SkScalar B = 2 * (src2w - C);
2780 return (A * t + B) * t + C;
2781}
2782
2783
2784static double conic_eval_denominator(SkScalar w, SkScalar t) {
2785 SkScalar B = 2 * (w - 1);
2786 SkScalar C = 1;
2787 SkScalar A = -B;
2788 return (A * t + B) * t + C;
2789}
2790
2791static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2792 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793 SkScalar y0 = pts[0].fY;
2794 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002795
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002796 int dir = 1;
2797 if (y0 > y2) {
2798 SkTSwap(y0, y2);
2799 dir = -1;
2800 }
caryclark9aacd902015-12-14 08:38:09 -08002801 if (y < y0 || y > y2) {
2802 return 0;
2803 }
caryclark9cb5d752015-12-18 04:35:24 -08002804 if (checkOnCurve(x, y, pts[0], pts[2])) {
2805 *onCurveCount += 1;
2806 return 0;
2807 }
caryclark9aacd902015-12-14 08:38:09 -08002808 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 return 0;
2810 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002811
caryclark9aacd902015-12-14 08:38:09 -08002812 SkScalar roots[2];
2813 SkScalar A = pts[2].fY;
2814 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2815 SkScalar C = pts[0].fY;
2816 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2817 B -= C; // B = b*w - w * yCept + yCept - a
2818 C -= y;
2819 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2820 SkASSERT(n <= 1);
2821 SkScalar xt;
2822 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002823 // zero roots are returned only when y0 == y
2824 // Need [0] if dir == 1
2825 // and [2] if dir == -1
2826 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002827 } else {
2828 SkScalar t = roots[0];
2829 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2830 }
2831 if (SkScalarNearlyEqual(xt, x)) {
2832 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2833 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002834 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002835 }
2836 }
2837 return xt < x ? dir : 0;
2838}
2839
2840static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2841 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2842 if (y0 == y1) {
2843 return true;
2844 }
2845 if (y0 < y1) {
2846 return y1 <= y2;
2847 } else {
2848 return y1 >= y2;
2849 }
2850}
2851
2852static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2853 int* onCurveCount) {
2854 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002855 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002856 // If the data points are very large, the conic may not be monotonic but may also
2857 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002858 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2859 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2860 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002861 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2862 }
2863 return w;
2864}
2865
2866static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2867 SkScalar y0 = pts[0].fY;
2868 SkScalar y2 = pts[2].fY;
2869
2870 int dir = 1;
2871 if (y0 > y2) {
2872 SkTSwap(y0, y2);
2873 dir = -1;
2874 }
2875 if (y < y0 || y > y2) {
2876 return 0;
2877 }
caryclark9cb5d752015-12-18 04:35:24 -08002878 if (checkOnCurve(x, y, pts[0], pts[2])) {
2879 *onCurveCount += 1;
2880 return 0;
2881 }
caryclark9aacd902015-12-14 08:38:09 -08002882 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002883 return 0;
2884 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 // bounds check on X (not required. is it faster?)
2886#if 0
2887 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2888 return 0;
2889 }
2890#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002891
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 SkScalar roots[2];
2893 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2894 2 * (pts[1].fY - pts[0].fY),
2895 pts[0].fY - y,
2896 roots);
2897 SkASSERT(n <= 1);
2898 SkScalar xt;
2899 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002900 // zero roots are returned only when y0 == y
2901 // Need [0] if dir == 1
2902 // and [2] if dir == -1
2903 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002904 } else {
2905 SkScalar t = roots[0];
2906 SkScalar C = pts[0].fX;
2907 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2908 SkScalar B = 2 * (pts[1].fX - C);
2909 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2910 }
caryclark9aacd902015-12-14 08:38:09 -08002911 if (SkScalarNearlyEqual(xt, x)) {
2912 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2913 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002914 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002915 }
2916 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002917 return xt < x ? dir : 0;
2918}
2919
caryclark9aacd902015-12-14 08:38:09 -08002920static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002921 SkPoint dst[5];
2922 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002923
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002924 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2925 n = SkChopQuadAtYExtrema(pts, dst);
2926 pts = dst;
2927 }
caryclark9aacd902015-12-14 08:38:09 -08002928 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002930 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002931 }
2932 return w;
2933}
2934
caryclark9aacd902015-12-14 08:38:09 -08002935static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 SkScalar x0 = pts[0].fX;
2937 SkScalar y0 = pts[0].fY;
2938 SkScalar x1 = pts[1].fX;
2939 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002940
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002941 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002942
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002943 int dir = 1;
2944 if (y0 > y1) {
2945 SkTSwap(y0, y1);
2946 dir = -1;
2947 }
caryclark9aacd902015-12-14 08:38:09 -08002948 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002949 return 0;
2950 }
caryclark9cb5d752015-12-18 04:35:24 -08002951 if (checkOnCurve(x, y, pts[0], pts[1])) {
2952 *onCurveCount += 1;
2953 return 0;
2954 }
2955 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002956 return 0;
2957 }
2958 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002959
caryclark9aacd902015-12-14 08:38:09 -08002960 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002961 // zero cross means the point is on the line, and since the case where
2962 // y of the query point is at the end point is handled above, we can be
2963 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002964 if (x != x1 || y != pts[1].fY) {
2965 *onCurveCount += 1;
2966 }
caryclark9aacd902015-12-14 08:38:09 -08002967 dir = 0;
2968 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002969 dir = 0;
2970 }
2971 return dir;
2972}
2973
caryclark9aacd902015-12-14 08:38:09 -08002974static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
2975 SkTDArray<SkVector>* tangents) {
2976 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
2977 && !between(pts[2].fY, y, pts[3].fY)) {
2978 return;
2979 }
2980 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
2981 && !between(pts[2].fX, x, pts[3].fX)) {
2982 return;
2983 }
2984 SkPoint dst[10];
2985 int n = SkChopCubicAtYExtrema(pts, dst);
2986 for (int i = 0; i <= n; ++i) {
2987 SkPoint* c = &dst[i * 3];
2988 SkScalar t;
2989 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
2990 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
2991 if (!SkScalarNearlyEqual(x, xt)) {
2992 continue;
2993 }
2994 SkVector tangent;
2995 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
2996 tangents->push(tangent);
2997 }
2998}
2999
3000static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3001 SkTDArray<SkVector>* tangents) {
3002 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3003 return;
3004 }
3005 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3006 return;
3007 }
3008 SkScalar roots[2];
3009 SkScalar A = pts[2].fY;
3010 SkScalar B = pts[1].fY * w - y * w + y;
3011 SkScalar C = pts[0].fY;
3012 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3013 B -= C; // B = b*w - w * yCept + yCept - a
3014 C -= y;
3015 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3016 for (int index = 0; index < n; ++index) {
3017 SkScalar t = roots[index];
3018 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3019 if (!SkScalarNearlyEqual(x, xt)) {
3020 continue;
3021 }
3022 SkConic conic(pts, w);
3023 tangents->push(conic.evalTangentAt(t));
3024 }
3025}
3026
3027static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3028 SkTDArray<SkVector>* tangents) {
3029 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3030 return;
3031 }
3032 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3033 return;
3034 }
3035 SkScalar roots[2];
3036 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3037 2 * (pts[1].fY - pts[0].fY),
3038 pts[0].fY - y,
3039 roots);
3040 for (int index = 0; index < n; ++index) {
3041 SkScalar t = roots[index];
3042 SkScalar C = pts[0].fX;
3043 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3044 SkScalar B = 2 * (pts[1].fX - C);
3045 SkScalar xt = (A * t + B) * t + C;
3046 if (!SkScalarNearlyEqual(x, xt)) {
3047 continue;
3048 }
3049 tangents->push(SkEvalQuadTangentAt(pts, t));
3050 }
3051}
3052
3053static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3054 SkTDArray<SkVector>* tangents) {
3055 SkScalar y0 = pts[0].fY;
3056 SkScalar y1 = pts[1].fY;
3057 if (!between(y0, y, y1)) {
3058 return;
3059 }
3060 SkScalar x0 = pts[0].fX;
3061 SkScalar x1 = pts[1].fX;
3062 if (!between(x0, x, x1)) {
3063 return;
3064 }
3065 SkScalar dx = x1 - x0;
3066 SkScalar dy = y1 - y0;
3067 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3068 return;
3069 }
3070 SkVector v;
3071 v.set(dx, dy);
3072 tangents->push(v);
3073}
3074
reed@google.com4db592c2013-10-30 17:39:43 +00003075static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3076 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3077}
3078
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003079bool SkPath::contains(SkScalar x, SkScalar y) const {
3080 bool isInverse = this->isInverseFillType();
3081 if (this->isEmpty()) {
3082 return isInverse;
3083 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003084
reed@google.com4db592c2013-10-30 17:39:43 +00003085 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003086 return isInverse;
3087 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003088
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003089 SkPath::Iter iter(*this, true);
3090 bool done = false;
3091 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003092 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003093 do {
3094 SkPoint pts[4];
3095 switch (iter.next(pts, false)) {
3096 case SkPath::kMove_Verb:
3097 case SkPath::kClose_Verb:
3098 break;
3099 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003100 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003101 break;
3102 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003103 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003104 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003105 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003106 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003107 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003108 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003109 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003110 break;
3111 case SkPath::kDone_Verb:
3112 done = true;
3113 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003114 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003115 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003116 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3117 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3118 if (evenOddFill) {
3119 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003120 }
caryclark9aacd902015-12-14 08:38:09 -08003121 if (w) {
3122 return !isInverse;
3123 }
3124 if (onCurveCount <= 1) {
3125 return SkToBool(onCurveCount) ^ isInverse;
3126 }
3127 if ((onCurveCount & 1) || evenOddFill) {
3128 return SkToBool(onCurveCount & 1) ^ isInverse;
3129 }
3130 // If the point touches an even number of curves, and the fill is winding, check for
3131 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3132 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003133 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003134 SkTDArray<SkVector> tangents;
3135 do {
3136 SkPoint pts[4];
3137 int oldCount = tangents.count();
3138 switch (iter.next(pts, false)) {
3139 case SkPath::kMove_Verb:
3140 case SkPath::kClose_Verb:
3141 break;
3142 case SkPath::kLine_Verb:
3143 tangent_line(pts, x, y, &tangents);
3144 break;
3145 case SkPath::kQuad_Verb:
3146 tangent_quad(pts, x, y, &tangents);
3147 break;
3148 case SkPath::kConic_Verb:
3149 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3150 break;
3151 case SkPath::kCubic_Verb:
3152 tangent_cubic(pts, x, y, &tangents);
3153 break;
3154 case SkPath::kDone_Verb:
3155 done = true;
3156 break;
3157 }
3158 if (tangents.count() > oldCount) {
3159 int last = tangents.count() - 1;
3160 const SkVector& tangent = tangents[last];
3161 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3162 tangents.remove(last);
3163 } else {
3164 for (int index = 0; index < last; ++index) {
3165 const SkVector& test = tangents[index];
3166 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003167 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3168 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003169 tangents.remove(last);
3170 tangents.removeShuffle(index);
3171 break;
3172 }
3173 }
3174 }
3175 }
3176 } while (!done);
3177 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003178}
fmalitaaa0df4e2015-12-01 09:13:23 -08003179
3180int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3181 SkScalar w, SkPoint pts[], int pow2) {
3182 const SkConic conic(p0, p1, p2, w);
3183 return conic.chopIntoQuadsPOW2(pts, pow2);
3184}