blob: 75a4cdcd7a25d7fc7eaf6c857eddb5668612ea2f [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -08009#include "SkCubicClipper.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000010#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080011#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070013#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700141 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700170 // Simulate fFirstDirection = that.fFirstDirection;
171 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700172 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173}
174
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000175bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000176 // note: don't need to look at isConvex or bounds, since just comparing the
177 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000178 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000179 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700184 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700188 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
189 uint8_t temp = fFirstDirection;
190 fFirstDirection.store(that.fFirstDirection.load());
191 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700192 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000196static inline bool check_edge_against_rect(const SkPoint& p0,
197 const SkPoint& p1,
198 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700199 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000200 const SkPoint* edgeBegin;
201 SkVector v;
reed026beb52015-06-10 14:23:15 -0700202 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000203 v = p1 - p0;
204 edgeBegin = &p0;
205 } else {
206 v = p0 - p1;
207 edgeBegin = &p1;
208 }
209 if (v.fX || v.fY) {
210 // check the cross product of v with the vec from edgeBegin to each rect corner
211 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
212 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
213 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
214 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
215 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
216 return false;
217 }
218 }
219 return true;
220}
221
222bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
223 // This only handles non-degenerate convex paths currently.
224 if (kConvex_Convexity != this->getConvexity()) {
225 return false;
226 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000227
reed026beb52015-06-10 14:23:15 -0700228 SkPathPriv::FirstDirection direction;
229 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000230 return false;
231 }
232
233 SkPoint firstPt;
234 SkPoint prevPt;
235 RawIter iter(*this);
236 SkPath::Verb verb;
237 SkPoint pts[4];
238 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000239 SkDEBUGCODE(int segmentCount = 0;)
240 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000241
242 while ((verb = iter.next(pts)) != kDone_Verb) {
243 int nextPt = -1;
244 switch (verb) {
245 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000246 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247 SkDEBUGCODE(++moveCnt);
248 firstPt = prevPt = pts[0];
249 break;
250 case kLine_Verb:
251 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000252 SkASSERT(moveCnt && !closeCount);
253 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 break;
255 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000256 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(moveCnt && !closeCount);
258 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000259 nextPt = 2;
260 break;
261 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkASSERT(moveCnt && !closeCount);
263 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000264 nextPt = 3;
265 break;
266 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000267 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 break;
269 default:
270 SkDEBUGFAIL("unknown verb");
271 }
272 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800273 if (SkPath::kConic_Verb == verb) {
274 SkConic orig;
275 orig.set(pts, iter.conicWeight());
276 SkPoint quadPts[5];
277 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenaa97a842016-01-22 06:50:25 -0800278 SK_ALWAYSBREAK(2 == count);
reed220f9262014-12-17 08:21:04 -0800279
280 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
281 return false;
282 }
283 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
284 return false;
285 }
286 } else {
287 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
288 return false;
289 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 }
291 prevPt = pts[nextPt];
292 }
293 }
294
295 return check_edge_against_rect(prevPt, firstPt, rect, direction);
296}
297
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000298uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000299 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800300#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
302 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
303#endif
304 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000305}
djsollen@google.come63793a2012-03-21 15:39:03 +0000306
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307void SkPath::reset() {
308 SkDEBUGCODE(this->validate();)
309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000310 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000311 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312}
313
314void SkPath::rewind() {
315 SkDEBUGCODE(this->validate();)
316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000317 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000318 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319}
320
fsb1475b02016-01-20 09:51:07 -0800321bool SkPath::isLastContourClosed() const {
322 int verbCount = fPathRef->countVerbs();
323 if (0 == verbCount) {
324 return false;
325 }
326 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
327}
328
reed@google.com7e6c4d12012-05-10 14:05:43 +0000329bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000330 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000331
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000332 if (2 == verbCount) {
333 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
334 if (kLine_Verb == fPathRef->atVerb(1)) {
335 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000336 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000337 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000338 line[0] = pts[0];
339 line[1] = pts[1];
340 }
341 return true;
342 }
343 }
344 return false;
345}
346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347/*
348 Determines if path is a rect by keeping track of changes in direction
349 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000350
caryclark@google.comf1316942011-07-26 19:54:45 +0000351 The direction is computed such that:
352 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000353 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000354 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000355 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000356
caryclark@google.comf1316942011-07-26 19:54:45 +0000357A rectangle cycles up/right/down/left or up/left/down/right.
358
359The test fails if:
360 The path is closed, and followed by a line.
361 A second move creates a new endpoint.
362 A diagonal line is parsed.
363 There's more than four changes of direction.
364 There's a discontinuity on the line (e.g., a move in the middle)
365 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000366 The path contains a quadratic or cubic.
367 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000368 *The rectangle doesn't complete a cycle.
369 *The final point isn't equal to the first point.
370
371 *These last two conditions we relax if we have a 3-edge path that would
372 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374It's OK if the path has:
375 Several colinear line segments composing a rectangle side.
376 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378The direction takes advantage of the corners found since opposite sides
379must travel in opposite directions.
380
381FIXME: Allow colinear quads and cubics to be treated like lines.
382FIXME: If the API passes fill-only, return true if the filled stroke
383 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000384
385 first,last,next direction state-machine:
386 0x1 is set if the segment is horizontal
387 0x2 is set if the segment is moving to the right or down
388 thus:
389 two directions are opposites iff (dirA ^ dirB) == 0x2
390 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000391
caryclark@google.comf1316942011-07-26 19:54:45 +0000392 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000393static int rect_make_dir(SkScalar dx, SkScalar dy) {
394 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
395}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000396bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
397 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 int corners = 0;
399 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000400 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700401 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000402 first.set(0, 0);
403 last.set(0, 0);
404 int firstDirection = 0;
405 int lastDirection = 0;
406 int nextDirection = 0;
407 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700409 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000410 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000411 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700412 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
413 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000415 savePts = pts;
416 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000417 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700418 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 case kLine_Verb: {
420 SkScalar left = last.fX;
421 SkScalar top = last.fY;
422 SkScalar right = pts->fX;
423 SkScalar bottom = pts->fY;
424 ++pts;
425 if (left != right && top != bottom) {
426 return false; // diagonal
427 }
428 if (left == right && top == bottom) {
429 break; // single point on side OK
430 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000431 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000432 if (0 == corners) {
433 firstDirection = nextDirection;
434 first = last;
435 last = pts[-1];
436 corners = 1;
437 closedOrMoved = false;
438 break;
439 }
440 if (closedOrMoved) {
441 return false; // closed followed by a line
442 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000443 if (autoClose && nextDirection == firstDirection) {
444 break; // colinear with first
445 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 closedOrMoved = autoClose;
447 if (lastDirection != nextDirection) {
448 if (++corners > 4) {
449 return false; // too many direction changes
450 }
451 }
452 last = pts[-1];
453 if (lastDirection == nextDirection) {
454 break; // colinear segment
455 }
456 // Possible values for corners are 2, 3, and 4.
457 // When corners == 3, nextDirection opposes firstDirection.
458 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000459 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
461 if ((directionCycle ^ turn) != nextDirection) {
462 return false; // direction didn't follow cycle
463 }
464 break;
465 }
466 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000467 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 case kCubic_Verb:
469 return false; // quadratic, cubic not allowed
470 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700471 if (allowPartial && !autoClose && firstDirection) {
472 insertClose = true;
473 *currVerb -= 1; // try move again afterwards
474 goto addMissingClose;
475 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 last = *pts++;
477 closedOrMoved = true;
478 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000479 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000480 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000481 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000483 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700485addMissingClose:
486 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 }
488 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000489 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000490 if (!result) {
491 // check if we are just an incomplete rectangle, in which case we can
492 // return true, but not claim to be closed.
493 // e.g.
494 // 3 sided rectangle
495 // 4 sided but the last edge is not long enough to reach the start
496 //
497 SkScalar closeX = first.x() - last.x();
498 SkScalar closeY = first.y() - last.y();
499 if (closeX && closeY) {
500 return false; // we're diagonal, abort (can we ever reach this?)
501 }
502 int closeDirection = rect_make_dir(closeX, closeY);
503 // make sure the close-segment doesn't double-back on itself
504 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
505 result = true;
506 autoClose = false; // we are not closed
507 }
508 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000509 if (savePts) {
510 *ptsPtr = savePts;
511 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000512 if (result && isClosed) {
513 *isClosed = autoClose;
514 }
515 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000516 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000517 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000518 return result;
519}
520
robertphillips4f662e62014-12-29 14:06:51 -0800521bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000522 SkDEBUGCODE(this->validate();)
523 int currVerb = 0;
524 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800525 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800526 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800527 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000528 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800529 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800530 int32_t num = SkToS32(pts - first);
531 if (num) {
532 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800533 } else {
534 // 'pts' isn't updated for open rects
535 *rect = this->getBounds();
536 }
537 }
538 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000539}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000540
caryclark95bc5f32015-04-08 08:34:15 -0700541bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000542 SkDEBUGCODE(this->validate();)
543 int currVerb = 0;
544 const SkPoint* pts = fPathRef->points();
545 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000546 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700547 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return false;
549 }
550 const SkPoint* last = pts;
551 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700552 bool isClosed;
553 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000554 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700555 if (!isClosed) {
556 pts = fPathRef->points() + fPathRef->countPoints();
557 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000558 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 if (testRects[0].contains(testRects[1])) {
560 if (rects) {
561 rects[0] = testRects[0];
562 rects[1] = testRects[1];
563 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000564 if (dirs) {
565 dirs[0] = testDirs[0];
566 dirs[1] = testDirs[1];
567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 return true;
569 }
570 if (testRects[1].contains(testRects[0])) {
571 if (rects) {
572 rects[0] = testRects[1];
573 rects[1] = testRects[0];
574 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000575 if (dirs) {
576 dirs[0] = testDirs[1];
577 dirs[1] = testDirs[0];
578 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000579 return true;
580 }
581 }
582 return false;
583}
584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585int SkPath::countPoints() const {
586 return fPathRef->countPoints();
587}
588
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000589int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000590 SkDEBUGCODE(this->validate();)
591
592 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000593 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800595 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000596 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597}
598
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000599SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000600 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
601 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000602 }
603 return SkPoint::Make(0, 0);
604}
605
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000606int SkPath::countVerbs() const {
607 return fPathRef->countVerbs();
608}
609
610static inline void copy_verbs_reverse(uint8_t* inorderDst,
611 const uint8_t* reversedSrc,
612 int count) {
613 for (int i = 0; i < count; ++i) {
614 inorderDst[i] = reversedSrc[~i];
615 }
616}
617
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000618int SkPath::getVerbs(uint8_t dst[], int max) const {
619 SkDEBUGCODE(this->validate();)
620
621 SkASSERT(max >= 0);
622 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 int count = SkMin32(max, fPathRef->countVerbs());
624 copy_verbs_reverse(dst, fPathRef->verbs(), count);
625 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000626}
627
reed@google.com294dd7b2011-10-11 11:58:32 +0000628bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 SkDEBUGCODE(this->validate();)
630
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000632 if (count > 0) {
633 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000636 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000638 if (lastPt) {
639 lastPt->set(0, 0);
640 }
641 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642}
643
caryclarkaec25102015-04-29 08:28:30 -0700644void SkPath::setPt(int index, SkScalar x, SkScalar y) {
645 SkDEBUGCODE(this->validate();)
646
647 int count = fPathRef->countPoints();
648 if (count <= index) {
649 return;
650 } else {
651 SkPathRef::Editor ed(&fPathRef);
652 ed.atPoint(index)->set(x, y);
653 }
654}
655
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656void SkPath::setLastPt(SkScalar x, SkScalar y) {
657 SkDEBUGCODE(this->validate();)
658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 if (count == 0) {
661 this->moveTo(x, y);
662 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 SkPathRef::Editor ed(&fPathRef);
664 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 }
666}
667
reed@google.com04863fa2011-05-15 04:08:24 +0000668void SkPath::setConvexity(Convexity c) {
669 if (fConvexity != c) {
670 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000671 }
672}
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674//////////////////////////////////////////////////////////////////////////////
675// Construction methods
676
reed026beb52015-06-10 14:23:15 -0700677#define DIRTY_AFTER_EDIT \
678 do { \
679 fConvexity = kUnknown_Convexity; \
680 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000681 } while (0)
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683void SkPath::incReserve(U16CPU inc) {
684 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkDEBUGCODE(this->validate();)
687}
688
689void SkPath::moveTo(SkScalar x, SkScalar y) {
690 SkDEBUGCODE(this->validate();)
691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000694 // remember our index
695 fLastMoveToIndex = fPathRef->countPoints();
696
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000697 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700698
699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rMoveTo(SkScalar x, SkScalar y) {
703 SkPoint pt;
704 this->getLastPt(&pt);
705 this->moveTo(pt.fX + x, pt.fY + y);
706}
707
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000708void SkPath::injectMoveToIfNeeded() {
709 if (fLastMoveToIndex < 0) {
710 SkScalar x, y;
711 if (fPathRef->countVerbs() == 0) {
712 x = y = 0;
713 } else {
714 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
715 x = pt.fX;
716 y = pt.fY;
717 }
718 this->moveTo(x, y);
719 }
720}
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722void SkPath::lineTo(SkScalar x, SkScalar y) {
723 SkDEBUGCODE(this->validate();)
724
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000725 this->injectMoveToIfNeeded();
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 SkPathRef::Editor ed(&fPathRef);
728 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729
reed@google.comb54455e2011-05-16 14:16:04 +0000730 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731}
732
733void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000734 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->lineTo(pt.fX + x, pt.fY + y);
738}
739
740void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000743 this->injectMoveToIfNeeded();
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000749
reed@google.comb54455e2011-05-16 14:16:04 +0000750 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751}
752
753void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000754 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755 SkPoint pt;
756 this->getLastPt(&pt);
757 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
758}
759
reed@google.com277c3f82013-05-31 15:17:50 +0000760void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
761 SkScalar w) {
762 // check for <= 0 or NaN with this test
763 if (!(w > 0)) {
764 this->lineTo(x2, y2);
765 } else if (!SkScalarIsFinite(w)) {
766 this->lineTo(x1, y1);
767 this->lineTo(x2, y2);
768 } else if (SK_Scalar1 == w) {
769 this->quadTo(x1, y1, x2, y2);
770 } else {
771 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 this->injectMoveToIfNeeded();
774
reed@google.com277c3f82013-05-31 15:17:50 +0000775 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000776 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000777 pts[0].set(x1, y1);
778 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000779
reed@google.com277c3f82013-05-31 15:17:50 +0000780 DIRTY_AFTER_EDIT;
781 }
782}
783
784void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
785 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000786 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000787 SkPoint pt;
788 this->getLastPt(&pt);
789 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
790}
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
793 SkScalar x3, SkScalar y3) {
794 SkDEBUGCODE(this->validate();)
795
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000796 this->injectMoveToIfNeeded();
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 SkPathRef::Editor ed(&fPathRef);
799 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 pts[0].set(x1, y1);
801 pts[1].set(x2, y2);
802 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803
reed@google.comb54455e2011-05-16 14:16:04 +0000804 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805}
806
807void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
808 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000809 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 SkPoint pt;
811 this->getLastPt(&pt);
812 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
813 pt.fX + x3, pt.fY + y3);
814}
815
816void SkPath::close() {
817 SkDEBUGCODE(this->validate();)
818
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000819 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 case kLine_Verb:
823 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000824 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000826 case kMove_Verb: {
827 SkPathRef::Editor ed(&fPathRef);
828 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000830 }
reed@google.com277c3f82013-05-31 15:17:50 +0000831 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000832 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000833 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000834 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000835 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000836 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 }
838 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839
840 // signal that we need a moveTo to follow us (unless we're done)
841#if 0
842 if (fLastMoveToIndex >= 0) {
843 fLastMoveToIndex = ~fLastMoveToIndex;
844 }
845#else
846 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
847#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848}
849
850///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000851
fmalitac08d53e2015-11-17 09:53:29 -0800852namespace {
853
854template <unsigned N>
855class PointIterator {
856public:
857 PointIterator(SkPath::Direction dir, unsigned startIndex)
858 : fCurrent(startIndex % N)
859 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
860
861 const SkPoint& current() const {
862 SkASSERT(fCurrent < N);
863 return fPts[fCurrent];
864 }
865
866 const SkPoint& next() {
867 fCurrent = (fCurrent + fAdvance) % N;
868 return this->current();
869 }
870
871protected:
872 SkPoint fPts[N];
873
874private:
875 unsigned fCurrent;
876 unsigned fAdvance;
877};
878
879class RectPointIterator : public PointIterator<4> {
880public:
881 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
882 : PointIterator(dir, startIndex) {
883
884 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
885 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
886 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
887 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
888 }
889};
890
891class OvalPointIterator : public PointIterator<4> {
892public:
893 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
894 : PointIterator(dir, startIndex) {
895
896 const SkScalar cx = oval.centerX();
897 const SkScalar cy = oval.centerY();
898
899 fPts[0] = SkPoint::Make(cx, oval.fTop);
900 fPts[1] = SkPoint::Make(oval.fRight, cy);
901 fPts[2] = SkPoint::Make(cx, oval.fBottom);
902 fPts[3] = SkPoint::Make(oval.fLeft, cy);
903 }
904};
905
906class RRectPointIterator : public PointIterator<8> {
907public:
908 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
909 : PointIterator(dir, startIndex) {
910
911 const SkRect& bounds = rrect.getBounds();
912 const SkScalar L = bounds.fLeft;
913 const SkScalar T = bounds.fTop;
914 const SkScalar R = bounds.fRight;
915 const SkScalar B = bounds.fBottom;
916
917 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
918 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
919 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
920 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
921 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
922 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
923 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
924 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
925 }
926};
927
928} // anonymous namespace
929
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000930static void assert_known_direction(int dir) {
931 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
932}
933
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800935 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936}
937
938void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
939 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800940 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
941}
942
943void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000944 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700945 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800946 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000947 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800948 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000949
fmalitac08d53e2015-11-17 09:53:29 -0800950 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000951
fmalitac08d53e2015-11-17 09:53:29 -0800952 const int kVerbs = 5; // moveTo + 3x lineTo + close
953 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954
fmalitac08d53e2015-11-17 09:53:29 -0800955 RectPointIterator iter(rect, dir, startIndex);
956
957 this->moveTo(iter.current());
958 this->lineTo(iter.next());
959 this->lineTo(iter.next());
960 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800962
963 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964}
965
reed@google.com744faba2012-05-29 19:54:52 +0000966void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
967 SkDEBUGCODE(this->validate();)
968 if (count <= 0) {
969 return;
970 }
971
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000972 fLastMoveToIndex = fPathRef->countPoints();
973
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000974 // +close makes room for the extra kClose_Verb
975 SkPathRef::Editor ed(&fPathRef, count+close, count);
976
977 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000978 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000979 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
980 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000981 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000982
reed@google.com744faba2012-05-29 19:54:52 +0000983 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000984 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800985 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000986 }
987
reed@google.com744faba2012-05-29 19:54:52 +0000988 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000989 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000990}
991
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000992#include "SkGeometry.h"
993
reedf90ea012015-01-29 12:03:58 -0800994static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
995 SkPoint* pt) {
996 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000997 // Chrome uses this path to move into and out of ovals. If not
998 // treated as a special case the moves can distort the oval's
999 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001000 pt->set(oval.fRight, oval.centerY());
1001 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001002 } else if (0 == oval.width() && 0 == oval.height()) {
1003 // Chrome will sometimes create 0 radius round rects. Having degenerate
1004 // quad segments in the path prevents the path from being recognized as
1005 // a rect.
1006 // TODO: optimizing the case where only one of width or height is zero
1007 // should also be considered. This case, however, doesn't seem to be
1008 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001009 pt->set(oval.fRight, oval.fTop);
1010 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001011 }
reedf90ea012015-01-29 12:03:58 -08001012 return false;
1013}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001014
reedd5d27d92015-02-09 13:54:43 -08001015// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1016//
1017static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1018 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1019 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1020 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001021
1022 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001023 loss in radians-conversion and/or sin/cos, we may end up with coincident
1024 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1025 of drawing a nearly complete circle (good).
1026 e.g. canvas.drawArc(0, 359.99, ...)
1027 -vs- canvas.drawArc(0, 359.9, ...)
1028 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001029 */
reedd5d27d92015-02-09 13:54:43 -08001030 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001031 SkScalar sw = SkScalarAbs(sweepAngle);
1032 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1033 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1034 // make a guess at a tiny angle (in radians) to tweak by
1035 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1036 // not sure how much will be enough, so we use a loop
1037 do {
1038 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001039 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1040 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 }
1042 }
reedd5d27d92015-02-09 13:54:43 -08001043 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1044}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045
reed9e779d42015-02-17 11:43:14 -08001046/**
1047 * If this returns 0, then the caller should just line-to the singlePt, else it should
1048 * ignore singlePt and append the specified number of conics.
1049 */
reedd5d27d92015-02-09 13:54:43 -08001050static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001051 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1052 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001053 SkMatrix matrix;
1054
1055 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1056 matrix.postTranslate(oval.centerX(), oval.centerY());
1057
reed9e779d42015-02-17 11:43:14 -08001058 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1059 if (0 == count) {
1060 matrix.mapXY(start.x(), start.y(), singlePt);
1061 }
1062 return count;
reedd5d27d92015-02-09 13:54:43 -08001063}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001065void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001067 SkRRect rrect;
1068 rrect.setRectRadii(rect, (const SkVector*) radii);
1069 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070}
1071
reed@google.com4ed0fb72012-12-12 20:48:18 +00001072void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001073 // legacy start indices: 6 (CW) and 7(CCW)
1074 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1075}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001076
fmalitac08d53e2015-11-17 09:53:29 -08001077void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1078 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001079
fmalitac08d53e2015-11-17 09:53:29 -08001080 if (rrect.isEmpty()) {
1081 return;
reed1b28a3a2014-12-17 14:42:09 -08001082 }
fmalitac08d53e2015-11-17 09:53:29 -08001083
caryclarkda707bf2015-11-19 14:47:43 -08001084 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001085 const SkRect& bounds = rrect.getBounds();
1086
1087 if (rrect.isRect()) {
1088 // degenerate(rect) => radii points are collapsing
1089 this->addRect(bounds, dir, (startIndex + 1) / 2);
1090 } else if (rrect.isOval()) {
1091 // degenerate(oval) => line points are collapsing
1092 this->addOval(bounds, dir, startIndex / 2);
1093 } else {
1094 fFirstDirection = this->hasOnlyMoveTos() ?
1095 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1096
1097 SkAutoPathBoundsUpdate apbu(this, bounds);
1098 SkAutoDisableDirectionCheck addc(this);
1099
1100 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1101 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1102 const SkScalar weight = SK_ScalarRoot2Over2;
1103
1104 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1105 const int kVerbs = startsWithConic
1106 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1107 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1108 this->incReserve(kVerbs);
1109
1110 RRectPointIterator rrectIter(rrect, dir, startIndex);
1111 // Corner iterator indices follow the collapsed radii model,
1112 // adjusted such that the start pt is "behind" the radii start pt.
1113 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1114 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1115
1116 this->moveTo(rrectIter.current());
1117 if (startsWithConic) {
1118 for (unsigned i = 0; i < 3; ++i) {
1119 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1120 this->lineTo(rrectIter.next());
1121 }
1122 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1123 // final lineTo handled by close().
1124 } else {
1125 for (unsigned i = 0; i < 4; ++i) {
1126 this->lineTo(rrectIter.next());
1127 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1128 }
1129 }
1130 this->close();
1131
caryclarkda707bf2015-11-19 14:47:43 -08001132 SkPathRef::Editor ed(&fPathRef);
1133 ed.setIsRRect(isRRect);
1134
fmalitac08d53e2015-11-17 09:53:29 -08001135 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1136 }
1137
1138 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001139}
1140
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001141bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001142 int count = fPathRef->countVerbs();
1143 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1144 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001145 if (*verbs == kLine_Verb ||
1146 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001147 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001148 *verbs == kCubic_Verb) {
1149 return false;
1150 }
1151 ++verbs;
1152 }
1153 return true;
1154}
1155
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001156void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1157 Direction dir) {
1158 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001159
humper@google.com75e3ca12013-04-08 21:44:11 +00001160 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001161 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001162 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001163 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001164 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1165 return;
1166 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001167
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001168 SkRRect rrect;
1169 rrect.setRectXY(rect, rx, ry);
1170 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001171}
1172
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001174 // legacy start index: 1
1175 this->addOval(oval, dir, 1);
1176}
1177
1178void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001179 assert_known_direction(dir);
1180
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181 /* If addOval() is called after previous moveTo(),
1182 this path is still marked as an oval. This is used to
1183 fit into WebKit's calling sequences.
1184 We can't simply check isEmpty() in this case, as additional
1185 moveTo() would mark the path non empty.
1186 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001187 bool isOval = hasOnlyMoveTos();
1188 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001189 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001190 } else {
reed026beb52015-06-10 14:23:15 -07001191 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001192 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001193
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001194 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195 SkAutoPathBoundsUpdate apbu(this, oval);
1196
fmalitac08d53e2015-11-17 09:53:29 -08001197 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1198 const int kVerbs = 6; // moveTo + 4x conicTo + close
1199 this->incReserve(kVerbs);
1200
1201 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1202 // The corner iterator pts are tracking "behind" the oval/radii pts.
1203 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001204 const SkScalar weight = SK_ScalarRoot2Over2;
1205
fmalitac08d53e2015-11-17 09:53:29 -08001206 this->moveTo(ovalIter.current());
1207 for (unsigned i = 0; i < 4; ++i) {
1208 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001209 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211
fmalitac08d53e2015-11-17 09:53:29 -08001212 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1213
robertphillips@google.com466310d2013-12-03 16:43:54 +00001214 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001215
robertphillips@google.com466310d2013-12-03 16:43:54 +00001216 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001217}
1218
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1220 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001221 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 }
1223}
1224
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1226 bool forceMoveTo) {
1227 if (oval.width() < 0 || oval.height() < 0) {
1228 return;
1229 }
1230
reedf90ea012015-01-29 12:03:58 -08001231 if (fPathRef->countVerbs() == 0) {
1232 forceMoveTo = true;
1233 }
1234
1235 SkPoint lonePt;
1236 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1237 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1238 return;
1239 }
1240
reedd5d27d92015-02-09 13:54:43 -08001241 SkVector startV, stopV;
1242 SkRotationDirection dir;
1243 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1244
reed9e779d42015-02-17 11:43:14 -08001245 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001246 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001247 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001248 if (count) {
1249 this->incReserve(count * 2 + 1);
1250 const SkPoint& pt = conics[0].fPts[0];
1251 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1252 for (int i = 0; i < count; ++i) {
1253 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1254 }
reed9e779d42015-02-17 11:43:14 -08001255 } else {
1256 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001257 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258}
1259
caryclark55d49052016-01-23 05:07:04 -08001260// This converts the SVG arc to conics.
1261// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1262// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1263// See also SVG implementation notes:
1264// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1265// Note that arcSweep bool value is flipped from the original implementation.
1266void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1267 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
1268 SkPoint srcPts[2];
1269 this->getLastPt(&srcPts[0]);
1270 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1271 // joining the endpoints.
1272 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1273 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001274 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001275 return;
1276 }
1277 // If the current point and target point for the arc are identical, it should be treated as a
1278 // zero length path. This ensures continuity in animations.
1279 srcPts[1].set(x, y);
1280 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001281 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001282 return;
1283 }
1284 rx = SkScalarAbs(rx);
1285 ry = SkScalarAbs(ry);
1286 SkVector midPointDistance = srcPts[0] - srcPts[1];
1287 midPointDistance *= 0.5f;
1288
1289 SkMatrix pointTransform;
1290 pointTransform.setRotate(-angle);
1291
1292 SkPoint transformedMidPoint;
1293 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1294 SkScalar squareRx = rx * rx;
1295 SkScalar squareRy = ry * ry;
1296 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1297 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1298
1299 // Check if the radii are big enough to draw the arc, scale radii if not.
1300 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1301 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1302 if (radiiScale > 1) {
1303 radiiScale = SkScalarSqrt(radiiScale);
1304 rx *= radiiScale;
1305 ry *= radiiScale;
1306 }
1307
1308 pointTransform.setScale(1 / rx, 1 / ry);
1309 pointTransform.preRotate(-angle);
1310
1311 SkPoint unitPts[2];
1312 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1313 SkVector delta = unitPts[1] - unitPts[0];
1314
1315 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1316 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1317
1318 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1319 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1320 scaleFactor = -scaleFactor;
1321 }
1322 delta.scale(scaleFactor);
1323 SkPoint centerPoint = unitPts[0] + unitPts[1];
1324 centerPoint *= 0.5f;
1325 centerPoint.offset(-delta.fY, delta.fX);
1326 unitPts[0] -= centerPoint;
1327 unitPts[1] -= centerPoint;
1328 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1329 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1330 SkScalar thetaArc = theta2 - theta1;
1331 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1332 thetaArc += SK_ScalarPI * 2;
1333 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1334 thetaArc -= SK_ScalarPI * 2;
1335 }
1336 pointTransform.setRotate(angle);
1337 pointTransform.preScale(rx, ry);
1338
1339 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1340 SkScalar thetaWidth = thetaArc / segments;
1341 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1342 if (!SkScalarIsFinite(t)) {
1343 return;
1344 }
1345 SkScalar startTheta = theta1;
1346 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1347 for (int i = 0; i < segments; ++i) {
1348 SkScalar endTheta = startTheta + thetaWidth;
1349 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1350
1351 unitPts[1].set(cosEndTheta, sinEndTheta);
1352 unitPts[1] += centerPoint;
1353 unitPts[0] = unitPts[1];
1354 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1355 SkPoint mapped[2];
1356 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1357 this->conicTo(mapped[0], mapped[1], w);
1358 startTheta = endTheta;
1359 }
1360}
1361
1362void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1363 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1364 SkPoint currentPoint;
1365 this->getLastPt(&currentPoint);
1366 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1367}
1368
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001369void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370 if (oval.isEmpty() || 0 == sweepAngle) {
1371 return;
1372 }
1373
1374 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1375
1376 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1377 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
reedc7789042015-01-29 12:59:11 -08001378 } else {
1379 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001381}
1382
1383/*
1384 Need to handle the case when the angle is sharp, and our computed end-points
1385 for the arc go behind pt1 and/or p2...
1386*/
reedc7789042015-01-29 12:59:11 -08001387void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001388 if (radius == 0) {
1389 this->lineTo(x1, y1);
1390 return;
1391 }
1392
1393 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001394
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 // need to know our prev pt so we can construct tangent vectors
1396 {
1397 SkPoint start;
1398 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001399 // Handle degenerate cases by adding a line to the first point and
1400 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401 before.setNormalize(x1 - start.fX, y1 - start.fY);
1402 after.setNormalize(x2 - x1, y2 - y1);
1403 }
reed@google.comabf15c12011-01-18 20:35:51 +00001404
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 SkScalar cosh = SkPoint::DotProduct(before, after);
1406 SkScalar sinh = SkPoint::CrossProduct(before, after);
1407
1408 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001409 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410 return;
1411 }
reed@google.comabf15c12011-01-18 20:35:51 +00001412
caryclark88651ae2016-01-20 11:55:11 -08001413 SkScalar dist = SkScalarAbs(SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414
1415 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1416 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
caryclark88651ae2016-01-20 11:55:11 -08001417#ifndef SK_SUPPORT_LEGACY_ARCTO
1418 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);
1422#else
reed@android.com8a1c16f2008-12-17 15:59:43 +00001423 SkRotationDirection arcDir;
1424
1425 // now turn before/after into normals
1426 if (sinh > 0) {
1427 before.rotateCCW();
1428 after.rotateCCW();
1429 arcDir = kCW_SkRotationDirection;
1430 } else {
1431 before.rotateCW();
1432 after.rotateCW();
1433 arcDir = kCCW_SkRotationDirection;
1434 }
1435
1436 SkMatrix matrix;
1437 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001438
reed@android.com8a1c16f2008-12-17 15:59:43 +00001439 matrix.setScale(radius, radius);
1440 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1441 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001442
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001444
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445 this->incReserve(count);
1446 // [xx,yy] == pts[0]
1447 this->lineTo(xx, yy);
1448 for (int i = 1; i < count; i += 2) {
1449 this->quadTo(pts[i], pts[i+1]);
1450 }
caryclark88651ae2016-01-20 11:55:11 -08001451#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452}
1453
1454///////////////////////////////////////////////////////////////////////////////
1455
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001456void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 SkMatrix matrix;
1458
1459 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001460 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461}
1462
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001463void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001466 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 SkPoint pts[4];
1468 Verb verb;
1469
1470 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001471 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472 while ((verb = iter.next(pts)) != kDone_Verb) {
1473 switch (verb) {
1474 case kMove_Verb:
1475 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001476 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1477 injectMoveToIfNeeded(); // In case last contour is closed
1478 this->lineTo(pts[0]);
1479 } else {
1480 this->moveTo(pts[0]);
1481 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 break;
1483 case kLine_Verb:
1484 proc(matrix, &pts[1], &pts[1], 1);
1485 this->lineTo(pts[1]);
1486 break;
1487 case kQuad_Verb:
1488 proc(matrix, &pts[1], &pts[1], 2);
1489 this->quadTo(pts[1], pts[2]);
1490 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001491 case kConic_Verb:
1492 proc(matrix, &pts[1], &pts[1], 2);
1493 this->conicTo(pts[1], pts[2], iter.conicWeight());
1494 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 case kCubic_Verb:
1496 proc(matrix, &pts[1], &pts[1], 3);
1497 this->cubicTo(pts[1], pts[2], pts[3]);
1498 break;
1499 case kClose_Verb:
1500 this->close();
1501 break;
1502 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001503 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001505 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506 }
1507}
1508
1509///////////////////////////////////////////////////////////////////////////////
1510
reed@google.com277c3f82013-05-31 15:17:50 +00001511static int pts_in_verb(unsigned verb) {
1512 static const uint8_t gPtsInVerb[] = {
1513 1, // kMove
1514 1, // kLine
1515 2, // kQuad
1516 2, // kConic
1517 3, // kCubic
1518 0, // kClose
1519 0 // kDone
1520 };
1521
1522 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1523 return gPtsInVerb[verb];
1524}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526// ignore the last point of the 1st contour
1527void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 int i, vcount = path.fPathRef->countVerbs();
1529 // exit early if the path is empty, or just has a moveTo.
1530 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 return;
1532 }
1533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001534 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001536 const uint8_t* verbs = path.fPathRef->verbs();
1537 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001538 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001540 SkASSERT(verbs[~0] == kMove_Verb);
1541 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001542 unsigned v = verbs[~i];
1543 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 if (n == 0) {
1545 break;
1546 }
1547 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001548 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 }
1550
1551 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 case kLine_Verb:
1554 this->lineTo(pts[-1].fX, pts[-1].fY);
1555 break;
1556 case kQuad_Verb:
1557 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1558 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001559 case kConic_Verb:
1560 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1561 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 case kCubic_Verb:
1563 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1564 pts[-3].fX, pts[-3].fY);
1565 break;
1566 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001567 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 break;
1569 }
reed@google.com277c3f82013-05-31 15:17:50 +00001570 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 }
1572}
1573
reed@google.com63d73742012-01-10 15:33:12 +00001574void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001575 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001576
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001577 const SkPoint* pts = src.fPathRef->pointsEnd();
1578 // we will iterator through src's verbs backwards
1579 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1580 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001581 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001582
1583 bool needMove = true;
1584 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001585 while (verbs < verbsEnd) {
1586 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001587 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001588
1589 if (needMove) {
1590 --pts;
1591 this->moveTo(pts->fX, pts->fY);
1592 needMove = false;
1593 }
1594 pts -= n;
1595 switch (v) {
1596 case kMove_Verb:
1597 if (needClose) {
1598 this->close();
1599 needClose = false;
1600 }
1601 needMove = true;
1602 pts += 1; // so we see the point in "if (needMove)" above
1603 break;
1604 case kLine_Verb:
1605 this->lineTo(pts[0]);
1606 break;
1607 case kQuad_Verb:
1608 this->quadTo(pts[1], pts[0]);
1609 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001610 case kConic_Verb:
1611 this->conicTo(pts[1], pts[0], *--conicWeights);
1612 break;
reed@google.com63d73742012-01-10 15:33:12 +00001613 case kCubic_Verb:
1614 this->cubicTo(pts[2], pts[1], pts[0]);
1615 break;
1616 case kClose_Verb:
1617 needClose = true;
1618 break;
1619 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001620 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001621 }
1622 }
1623}
1624
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625///////////////////////////////////////////////////////////////////////////////
1626
1627void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1628 SkMatrix matrix;
1629
1630 matrix.setTranslate(dx, dy);
1631 this->transform(matrix, dst);
1632}
1633
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1635 int level = 2) {
1636 if (--level >= 0) {
1637 SkPoint tmp[7];
1638
1639 SkChopCubicAtHalf(pts, tmp);
1640 subdivide_cubic_to(path, &tmp[0], level);
1641 subdivide_cubic_to(path, &tmp[3], level);
1642 } else {
1643 path->cubicTo(pts[1], pts[2], pts[3]);
1644 }
1645}
1646
1647void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1648 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001649 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 dst = (SkPath*)this;
1651 }
1652
tomhudson@google.com8d430182011-06-06 19:11:19 +00001653 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 SkPath tmp;
1655 tmp.fFillType = fFillType;
1656
1657 SkPath::Iter iter(*this, false);
1658 SkPoint pts[4];
1659 SkPath::Verb verb;
1660
reed@google.com4a3b7142012-05-16 17:16:46 +00001661 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 switch (verb) {
1663 case kMove_Verb:
1664 tmp.moveTo(pts[0]);
1665 break;
1666 case kLine_Verb:
1667 tmp.lineTo(pts[1]);
1668 break;
1669 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001670 // promote the quad to a conic
1671 tmp.conicTo(pts[1], pts[2],
1672 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001674 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001675 tmp.conicTo(pts[1], pts[2],
1676 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001677 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 case kCubic_Verb:
1679 subdivide_cubic_to(&tmp, pts);
1680 break;
1681 case kClose_Verb:
1682 tmp.close();
1683 break;
1684 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001685 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 break;
1687 }
1688 }
1689
1690 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001691 SkPathRef::Editor ed(&dst->fPathRef);
1692 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001693 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001695 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1696
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001699 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001700 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001702
reed026beb52015-06-10 14:23:15 -07001703 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1704 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001705 } else {
1706 SkScalar det2x2 =
1707 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1708 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1709 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001710 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1711 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001712 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001713 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001714 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001715 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001716 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001717 }
1718 }
1719
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 SkDEBUGCODE(dst->validate();)
1721 }
1722}
1723
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724///////////////////////////////////////////////////////////////////////////////
1725///////////////////////////////////////////////////////////////////////////////
1726
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001728 kEmptyContour_SegmentState, // The current contour is empty. We may be
1729 // starting processing or we may have just
1730 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001731 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1732 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1733 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734};
1735
1736SkPath::Iter::Iter() {
1737#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001738 fPts = nullptr;
1739 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001741 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001742 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743#endif
1744 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001745 fVerbs = nullptr;
1746 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 fNeedClose = false;
1748}
1749
1750SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1751 this->setPath(path, forceClose);
1752}
1753
1754void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001755 fPts = path.fPathRef->points();
1756 fVerbs = path.fPathRef->verbs();
1757 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001758 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001759 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001760 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 fForceClose = SkToU8(forceClose);
1762 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001763 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764}
1765
1766bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001767 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 return false;
1769 }
1770 if (fForceClose) {
1771 return true;
1772 }
1773
1774 const uint8_t* verbs = fVerbs;
1775 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001777 if (kMove_Verb == *(verbs - 1)) {
1778 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 }
1780
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001781 while (verbs > stop) {
1782 // verbs points one beyond the current verb, decrement first.
1783 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 if (kMove_Verb == v) {
1785 break;
1786 }
1787 if (kClose_Verb == v) {
1788 return true;
1789 }
1790 }
1791 return false;
1792}
1793
1794SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001795 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001796 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001797 // A special case: if both points are NaN, SkPoint::operation== returns
1798 // false, but the iterator expects that they are treated as the same.
1799 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001800 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1801 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001802 return kClose_Verb;
1803 }
1804
reed@google.com9e25dbf2012-05-15 17:05:38 +00001805 pts[0] = fLastPt;
1806 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 fLastPt = fMoveTo;
1808 fCloseLine = true;
1809 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001810 } else {
1811 pts[0] = fMoveTo;
1812 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814}
1815
reed@google.com9e25dbf2012-05-15 17:05:38 +00001816const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 if (fSegmentState == kAfterMove_SegmentState) {
1818 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001820 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1823 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001824 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826}
1827
caryclarke8c56662015-07-14 11:19:26 -07001828void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829 // We need to step over anything that will not move the current draw point
1830 // forward before the next move is seen
1831 const uint8_t* lastMoveVerb = 0;
1832 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001833 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 SkPoint lastPt = fLastPt;
1835 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 switch (verb) {
1838 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 // Keep a record of this most recent move
1840 lastMoveVerb = fVerbs;
1841 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001842 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001843 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001844 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 fPts++;
1846 break;
1847
1848 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001849 // A close when we are in a segment is always valid except when it
1850 // follows a move which follows a segment.
1851 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 return;
1853 }
1854 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 break;
1857
1858 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001859 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 if (lastMoveVerb) {
1861 fVerbs = lastMoveVerb;
1862 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001863 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 return;
1865 }
1866 return;
1867 }
1868 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001869 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 fPts++;
1871 break;
1872
reed@google.com277c3f82013-05-31 15:17:50 +00001873 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001875 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 if (lastMoveVerb) {
1877 fVerbs = lastMoveVerb;
1878 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001879 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001880 return;
1881 }
1882 return;
1883 }
1884 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001887 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 break;
1889
1890 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001891 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 if (lastMoveVerb) {
1893 fVerbs = lastMoveVerb;
1894 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001895 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001896 return;
1897 }
1898 return;
1899 }
1900 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001901 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 fPts += 3;
1903 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001904
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001906 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 }
1908 }
1909}
1910
reed@google.com4a3b7142012-05-16 17:16:46 +00001911SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001912 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 // Close the curve if requested and if there is some curve to close
1916 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001917 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 return kLine_Verb;
1919 }
1920 fNeedClose = false;
1921 return kClose_Verb;
1922 }
1923 return kDone_Verb;
1924 }
1925
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001926 // fVerbs is one beyond the current verb, decrement first
1927 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001928 const SkPoint* SK_RESTRICT srcPts = fPts;
1929 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930
1931 switch (verb) {
1932 case kMove_Verb:
1933 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001934 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 verb = this->autoClose(pts);
1936 if (verb == kClose_Verb) {
1937 fNeedClose = false;
1938 }
1939 return (Verb)verb;
1940 }
1941 if (fVerbs == fVerbStop) { // might be a trailing moveto
1942 return kDone_Verb;
1943 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001944 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001945 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001947 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 fNeedClose = fForceClose;
1950 break;
1951 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001952 pts[0] = this->cons_moveTo();
1953 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 fLastPt = srcPts[0];
1955 fCloseLine = false;
1956 srcPts += 1;
1957 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001958 case kConic_Verb:
1959 fConicWeights += 1;
1960 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001962 pts[0] = this->cons_moveTo();
1963 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 fLastPt = srcPts[1];
1965 srcPts += 2;
1966 break;
1967 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001968 pts[0] = this->cons_moveTo();
1969 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 fLastPt = srcPts[2];
1971 srcPts += 3;
1972 break;
1973 case kClose_Verb:
1974 verb = this->autoClose(pts);
1975 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001976 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001977 } else {
1978 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001979 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001980 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001981 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001982 break;
1983 }
1984 fPts = srcPts;
1985 return (Verb)verb;
1986}
1987
1988///////////////////////////////////////////////////////////////////////////////
1989
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001991 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992*/
1993
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001994size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 SkDEBUGCODE(this->validate();)
1996
halcanary96fcdcc2015-08-27 07:41:13 -07001997 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001998 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001999 return SkAlign4(byteCount);
2000 }
2001
2002 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002003
robertphillips@google.com466310d2013-12-03 16:43:54 +00002004 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002005 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002006 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002007 (fIsVolatile << kIsVolatile_SerializationShift) |
2008 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002009
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002010 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002011
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002012 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002013
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002014 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002015 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016}
2017
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002018size_t SkPath::readFromMemory(const void* storage, size_t length) {
2019 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002020
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002021 int32_t packed;
2022 if (!buffer.readS32(&packed)) {
2023 return 0;
2024 }
2025
reed8f086022015-06-11 14:22:19 -07002026 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07002027
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002028 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2029 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07002030 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002031 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002032 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002033 if (!pathRef) {
2034 return 0;
2035 }
2036
2037 fPathRef.reset(pathRef);
2038 SkDEBUGCODE(this->validate();)
2039 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002040
reed8f086022015-06-11 14:22:19 -07002041 // compatibility check
2042 if (version < kPathPrivFirstDirection_Version) {
2043 switch (dir) { // old values
2044 case 0:
2045 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2046 break;
2047 case 1:
2048 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2049 break;
2050 case 2:
2051 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2052 break;
2053 default:
2054 SkASSERT(false);
2055 }
2056 } else {
2057 fFirstDirection = dir;
2058 }
2059
ajumaf8aec582016-01-13 13:46:31 -08002060 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061}
2062
2063///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064
reede05fed02014-12-15 07:59:53 -08002065#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002066#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002067
reed@google.com51bbe752013-01-17 22:07:50 +00002068static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002069 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002070 str->append(label);
2071 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002072
reed@google.com51bbe752013-01-17 22:07:50 +00002073 const SkScalar* values = &pts[0].fX;
2074 count *= 2;
2075
2076 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002077 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002078 if (i < count - 1) {
2079 str->append(", ");
2080 }
2081 }
reed@google.com277c3f82013-05-31 15:17:50 +00002082 if (conicWeight >= 0) {
2083 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002084 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002085 }
caryclark08fa28c2014-10-23 13:08:56 -07002086 str->append(");");
reede05fed02014-12-15 07:59:53 -08002087 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002088 str->append(" // ");
2089 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002090 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002091 if (i < count - 1) {
2092 str->append(", ");
2093 }
2094 }
2095 if (conicWeight >= 0) {
2096 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002097 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002098 }
2099 }
2100 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002101}
2102
caryclarke9562592014-09-15 09:26:09 -07002103void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002104 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 Iter iter(*this, forceClose);
2106 SkPoint pts[4];
2107 Verb verb;
2108
caryclark66a5d8b2014-06-24 08:30:15 -07002109 if (!wStream) {
2110 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2111 }
reed@google.com51bbe752013-01-17 22:07:50 +00002112 SkString builder;
2113
reed@google.com4a3b7142012-05-16 17:16:46 +00002114 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 switch (verb) {
2116 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002117 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 break;
2119 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002120 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 break;
2122 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002123 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002125 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002126 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002127 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002129 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 break;
2131 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002132 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 break;
2134 default:
2135 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2136 verb = kDone_Verb; // stop the loop
2137 break;
2138 }
caryclark1049f122015-04-20 08:31:59 -07002139 if (!wStream && builder.size()) {
2140 SkDebugf("%s", builder.c_str());
2141 builder.reset();
2142 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 }
caryclark66a5d8b2014-06-24 08:30:15 -07002144 if (wStream) {
2145 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002146 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147}
2148
reed@android.come522ca52009-11-23 20:10:41 +00002149void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002150 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002151}
2152
2153void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002154 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002155}
2156
2157#ifdef SK_DEBUG
2158void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002159 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002160
djsollen@google.com077348c2012-10-22 20:23:32 +00002161#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002162 if (!fBoundsIsDirty) {
2163 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002164
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002165 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002166 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002167
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002168 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002169 // if we're empty, fBounds may be empty but translated, so we can't
2170 // necessarily compare to bounds directly
2171 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2172 // be [2, 2, 2, 2]
2173 SkASSERT(bounds.isEmpty());
2174 SkASSERT(fBounds.isEmpty());
2175 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002176 if (bounds.isEmpty()) {
2177 SkASSERT(fBounds.isEmpty());
2178 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002179 if (!fBounds.isEmpty()) {
2180 SkASSERT(fBounds.contains(bounds));
2181 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002182 }
reed@android.come522ca52009-11-23 20:10:41 +00002183 }
2184 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002185#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002186}
djsollen@google.com077348c2012-10-22 20:23:32 +00002187#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002188
reed@google.com04863fa2011-05-15 04:08:24 +00002189///////////////////////////////////////////////////////////////////////////////
2190
reed@google.com0b7b9822011-05-16 12:29:27 +00002191static int sign(SkScalar x) { return x < 0; }
2192#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002193
robertphillipsc506e302014-09-16 09:43:31 -07002194enum DirChange {
2195 kLeft_DirChange,
2196 kRight_DirChange,
2197 kStraight_DirChange,
2198 kBackwards_DirChange,
2199
2200 kInvalid_DirChange
2201};
2202
2203
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002204static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002205 // The error epsilon was empirically derived; worse case round rects
2206 // with a mid point outset by 2x float epsilon in tests had an error
2207 // of 12.
2208 const int epsilon = 16;
2209 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2210 return false;
2211 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002212 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002213 int aBits = SkFloatAs2sCompliment(compA);
2214 int bBits = SkFloatAs2sCompliment(compB);
2215 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002216}
2217
caryclarkb4216502015-03-02 13:02:34 -08002218static bool approximately_zero_when_compared_to(double x, double y) {
2219 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002220}
2221
caryclarkb4216502015-03-02 13:02:34 -08002222
reed@google.com04863fa2011-05-15 04:08:24 +00002223// only valid for a single contour
2224struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002225 Convexicator()
2226 : fPtCount(0)
2227 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002228 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002229 , fIsFinite(true)
2230 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002231 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002232 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002233 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002234 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002235 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002236 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002237
2238 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002239 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002240 }
2241
2242 SkPath::Convexity getConvexity() const { return fConvexity; }
2243
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002244 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002245 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002246
reed@google.com04863fa2011-05-15 04:08:24 +00002247 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002248 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002249 return;
2250 }
2251
2252 if (0 == fPtCount) {
2253 fCurrPt = pt;
2254 ++fPtCount;
2255 } else {
2256 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002257 SkScalar lengthSqd = vec.lengthSqd();
2258 if (!SkScalarIsFinite(lengthSqd)) {
2259 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002260 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002261 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002262 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002263 fCurrPt = pt;
2264 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002265 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 } else {
2267 SkASSERT(fPtCount > 2);
2268 this->addVec(vec);
2269 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002270
reed@google.com85b6e392011-05-15 20:25:17 +00002271 int sx = sign(vec.fX);
2272 int sy = sign(vec.fY);
2273 fDx += (sx != fSx);
2274 fDy += (sy != fSy);
2275 fSx = sx;
2276 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002277
reed@google.com85b6e392011-05-15 20:25:17 +00002278 if (fDx > 3 || fDy > 3) {
2279 fConvexity = SkPath::kConcave_Convexity;
2280 }
reed@google.com04863fa2011-05-15 04:08:24 +00002281 }
2282 }
2283 }
2284
2285 void close() {
2286 if (fPtCount > 2) {
2287 this->addVec(fFirstVec);
2288 }
2289 }
2290
caryclarkb4216502015-03-02 13:02:34 -08002291 DirChange directionChange(const SkVector& curVec) {
2292 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2293
2294 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2295 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2296 largest = SkTMax(largest, -smallest);
2297
2298 if (!almost_equal(largest, largest + cross)) {
2299 int sign = SkScalarSignAsInt(cross);
2300 if (sign) {
2301 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2302 }
2303 }
2304
2305 if (cross) {
2306 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2307 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2308 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2309 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2310 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2311 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2312 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2313 if (sign) {
2314 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2315 }
2316 }
2317 }
2318
2319 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2320 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2321 fLastVec.dot(curVec) < 0.0f) {
2322 return kBackwards_DirChange;
2323 }
2324
2325 return kStraight_DirChange;
2326 }
2327
2328
caryclarkd3d1a982014-12-08 04:57:38 -08002329 bool isFinite() const {
2330 return fIsFinite;
2331 }
2332
caryclark5ccef572015-03-02 10:07:56 -08002333 void setCurve(bool isCurve) {
2334 fIsCurve = isCurve;
2335 }
2336
reed@google.com04863fa2011-05-15 04:08:24 +00002337private:
2338 void addVec(const SkVector& vec) {
2339 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002340 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002341 switch (dir) {
2342 case kLeft_DirChange: // fall through
2343 case kRight_DirChange:
2344 if (kInvalid_DirChange == fExpectedDir) {
2345 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002346 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2347 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002348 } else if (dir != fExpectedDir) {
2349 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002350 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002351 }
2352 fLastVec = vec;
2353 break;
2354 case kStraight_DirChange:
2355 break;
2356 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002357 if (fIsCurve) {
2358 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002359 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002360 }
robertphillipsc506e302014-09-16 09:43:31 -07002361 fLastVec = vec;
2362 break;
2363 case kInvalid_DirChange:
2364 SkFAIL("Use of invalid direction change flag");
2365 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002366 }
2367 }
2368
caryclarkb4216502015-03-02 13:02:34 -08002369 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002370 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002371 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002372 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2373 // value with the current vec is deemed to be of a significant value.
2374 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002375 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002376 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002377 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002378 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002379 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002380 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002381 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002382};
2383
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002384SkPath::Convexity SkPath::internalGetConvexity() const {
2385 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002386 SkPoint pts[4];
2387 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002389
2390 int contourCount = 0;
2391 int count;
2392 Convexicator state;
2393
caryclarkd3d1a982014-12-08 04:57:38 -08002394 if (!isFinite()) {
2395 return kUnknown_Convexity;
2396 }
caryclarke8c56662015-07-14 11:19:26 -07002397 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002398 switch (verb) {
2399 case kMove_Verb:
2400 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002401 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002402 return kConcave_Convexity;
2403 }
2404 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002405 // fall through
2406 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002407 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002408 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002409 break;
caryclark5ccef572015-03-02 10:07:56 -08002410 case kQuad_Verb:
2411 // fall through
2412 case kConic_Verb:
2413 // fall through
2414 case kCubic_Verb:
2415 count = 2 + (kCubic_Verb == verb);
2416 // As an additional enhancement, this could set curve true only
2417 // if the curve is nonlinear
2418 state.setCurve(true);
2419 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002420 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002421 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002422 state.close();
2423 count = 0;
2424 break;
2425 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002426 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002427 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002428 return kConcave_Convexity;
2429 }
2430
2431 for (int i = 1; i <= count; i++) {
2432 state.addPt(pts[i]);
2433 }
2434 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002435 if (!state.isFinite()) {
2436 return kUnknown_Convexity;
2437 }
reed@google.com04863fa2011-05-15 04:08:24 +00002438 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002439 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002440 return kConcave_Convexity;
2441 }
2442 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002443 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002444 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2445 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002446 }
2447 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002448}
reed@google.com69a99432012-01-10 18:00:10 +00002449
2450///////////////////////////////////////////////////////////////////////////////
2451
2452class ContourIter {
2453public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002454 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002455
2456 bool done() const { return fDone; }
2457 // if !done() then these may be called
2458 int count() const { return fCurrPtCount; }
2459 const SkPoint* pts() const { return fCurrPt; }
2460 void next();
2461
2462private:
2463 int fCurrPtCount;
2464 const SkPoint* fCurrPt;
2465 const uint8_t* fCurrVerb;
2466 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002467 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002468 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002469 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002470};
2471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002472ContourIter::ContourIter(const SkPathRef& pathRef) {
2473 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002474 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002475 fCurrPt = pathRef.points();
2476 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002477 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002478 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002479 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002480 this->next();
2481}
2482
2483void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002484 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002485 fDone = true;
2486 }
2487 if (fDone) {
2488 return;
2489 }
2490
2491 // skip pts of prev contour
2492 fCurrPt += fCurrPtCount;
2493
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002494 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002495 int ptCount = 1; // moveTo
2496 const uint8_t* verbs = fCurrVerb;
2497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002498 for (--verbs; verbs > fStopVerbs; --verbs) {
2499 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002500 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002501 goto CONTOUR_END;
2502 case SkPath::kLine_Verb:
2503 ptCount += 1;
2504 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002505 case SkPath::kConic_Verb:
2506 fCurrConicWeight += 1;
2507 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002508 case SkPath::kQuad_Verb:
2509 ptCount += 2;
2510 break;
2511 case SkPath::kCubic_Verb:
2512 ptCount += 3;
2513 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002514 case SkPath::kClose_Verb:
2515 break;
2516 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002517 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002518 break;
2519 }
2520 }
2521CONTOUR_END:
2522 fCurrPtCount = ptCount;
2523 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002524 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002525}
2526
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002527// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002528static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002529 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2530 // We may get 0 when the above subtracts underflow. We expect this to be
2531 // very rare and lazily promote to double.
2532 if (0 == cross) {
2533 double p0x = SkScalarToDouble(p0.fX);
2534 double p0y = SkScalarToDouble(p0.fY);
2535
2536 double p1x = SkScalarToDouble(p1.fX);
2537 double p1y = SkScalarToDouble(p1.fY);
2538
2539 double p2x = SkScalarToDouble(p2.fX);
2540 double p2y = SkScalarToDouble(p2.fY);
2541
2542 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2543 (p1y - p0y) * (p2x - p0x));
2544
2545 }
2546 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002547}
2548
reed@google.comc1ea60a2012-01-31 15:15:36 +00002549// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002550static int find_max_y(const SkPoint pts[], int count) {
2551 SkASSERT(count > 0);
2552 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002553 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002554 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002555 SkScalar y = pts[i].fY;
2556 if (y > max) {
2557 max = y;
2558 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002559 }
2560 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002561 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002562}
2563
reed@google.comcabaf1d2012-01-11 21:03:05 +00002564static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2565 int i = index;
2566 for (;;) {
2567 i = (i + inc) % n;
2568 if (i == index) { // we wrapped around, so abort
2569 break;
2570 }
2571 if (pts[index] != pts[i]) { // found a different point, success!
2572 break;
2573 }
2574 }
2575 return i;
2576}
2577
reed@google.comc1ea60a2012-01-31 15:15:36 +00002578/**
2579 * Starting at index, and moving forward (incrementing), find the xmin and
2580 * xmax of the contiguous points that have the same Y.
2581 */
2582static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2583 int* maxIndexPtr) {
2584 const SkScalar y = pts[index].fY;
2585 SkScalar min = pts[index].fX;
2586 SkScalar max = min;
2587 int minIndex = index;
2588 int maxIndex = index;
2589 for (int i = index + 1; i < n; ++i) {
2590 if (pts[i].fY != y) {
2591 break;
2592 }
2593 SkScalar x = pts[i].fX;
2594 if (x < min) {
2595 min = x;
2596 minIndex = i;
2597 } else if (x > max) {
2598 max = x;
2599 maxIndex = i;
2600 }
2601 }
2602 *maxIndexPtr = maxIndex;
2603 return minIndex;
2604}
2605
reed026beb52015-06-10 14:23:15 -07002606static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2607 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002608}
2609
reed@google.comac8543f2012-01-30 20:51:25 +00002610/*
2611 * We loop through all contours, and keep the computed cross-product of the
2612 * contour that contained the global y-max. If we just look at the first
2613 * contour, we may find one that is wound the opposite way (correctly) since
2614 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2615 * that is outer most (or at least has the global y-max) before we can consider
2616 * its cross product.
2617 */
reed026beb52015-06-10 14:23:15 -07002618bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002619 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2620 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002621 return true;
2622 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002623
2624 // don't want to pay the cost for computing this if it
2625 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002626 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2627 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002628 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002629 return false;
2630 }
reed@google.com69a99432012-01-10 18:00:10 +00002631
reed026beb52015-06-10 14:23:15 -07002632 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002633
reed@google.comac8543f2012-01-30 20:51:25 +00002634 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002635 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002636 SkScalar ymaxCross = 0;
2637
reed@google.com69a99432012-01-10 18:00:10 +00002638 for (; !iter.done(); iter.next()) {
2639 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002640 if (n < 3) {
2641 continue;
2642 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002643
reed@google.comcabaf1d2012-01-11 21:03:05 +00002644 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002645 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002646 int index = find_max_y(pts, n);
2647 if (pts[index].fY < ymax) {
2648 continue;
2649 }
2650
2651 // If there is more than 1 distinct point at the y-max, we take the
2652 // x-min and x-max of them and just subtract to compute the dir.
2653 if (pts[(index + 1) % n].fY == pts[index].fY) {
2654 int maxIndex;
2655 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2656 if (minIndex == maxIndex) {
2657 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002658 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002659 SkASSERT(pts[minIndex].fY == pts[index].fY);
2660 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2661 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2662 // we just subtract the indices, and let that auto-convert to
2663 // SkScalar, since we just want - or + to signal the direction.
2664 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002665 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002666 TRY_CROSSPROD:
2667 // Find a next and prev index to use for the cross-product test,
2668 // but we try to find pts that form non-zero vectors from pts[index]
2669 //
2670 // Its possible that we can't find two non-degenerate vectors, so
2671 // we have to guard our search (e.g. all the pts could be in the
2672 // same place).
2673
2674 // we pass n - 1 instead of -1 so we don't foul up % operator by
2675 // passing it a negative LH argument.
2676 int prev = find_diff_pt(pts, index, n, n - 1);
2677 if (prev == index) {
2678 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002679 continue;
2680 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002681 int next = find_diff_pt(pts, index, n, 1);
2682 SkASSERT(next != index);
2683 cross = cross_prod(pts[prev], pts[index], pts[next]);
2684 // if we get a zero and the points are horizontal, then we look at the spread in
2685 // x-direction. We really should continue to walk away from the degeneracy until
2686 // there is a divergence.
2687 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2688 // construct the subtract so we get the correct Direction below
2689 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002690 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002691 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002692
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002693 if (cross) {
2694 // record our best guess so far
2695 ymax = pts[index].fY;
2696 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002697 }
2698 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002699 if (ymaxCross) {
2700 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002701 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002702 return true;
2703 } else {
2704 return false;
2705 }
reed@google.comac8543f2012-01-30 20:51:25 +00002706}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002707
2708///////////////////////////////////////////////////////////////////////////////
2709
caryclark9aacd902015-12-14 08:38:09 -08002710static bool between(SkScalar a, SkScalar b, SkScalar c) {
2711 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2712 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2713 return (a - b) * (c - b) <= 0;
2714}
2715
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002716static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2717 SkScalar D, SkScalar t) {
2718 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2719}
2720
2721static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2722 SkScalar t) {
2723 SkScalar A = c3 + 3*(c1 - c2) - c0;
2724 SkScalar B = 3*(c2 - c1 - c1 + c0);
2725 SkScalar C = 3*(c1 - c0);
2726 SkScalar D = c0;
2727 return eval_cubic_coeff(A, B, C, D, t);
2728}
2729
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002730template <size_t N> static void find_minmax(const SkPoint pts[],
2731 SkScalar* minPtr, SkScalar* maxPtr) {
2732 SkScalar min, max;
2733 min = max = pts[0].fX;
2734 for (size_t i = 1; i < N; ++i) {
2735 min = SkMinScalar(min, pts[i].fX);
2736 max = SkMaxScalar(max, pts[i].fX);
2737 }
2738 *minPtr = min;
2739 *maxPtr = max;
2740}
2741
caryclark9cb5d752015-12-18 04:35:24 -08002742static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2743 if (start.fY == end.fY) {
2744 return between(start.fX, x, end.fX) && x != end.fX;
2745 } else {
2746 return x == start.fX && y == start.fY;
2747 }
2748}
2749
caryclark9aacd902015-12-14 08:38:09 -08002750static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002751 SkScalar y0 = pts[0].fY;
2752 SkScalar y3 = pts[3].fY;
2753
2754 int dir = 1;
2755 if (y0 > y3) {
2756 SkTSwap(y0, y3);
2757 dir = -1;
2758 }
2759 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760 return 0;
2761 }
caryclark9cb5d752015-12-18 04:35:24 -08002762 if (checkOnCurve(x, y, pts[0], pts[3])) {
2763 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002764 return 0;
2765 }
caryclark9cb5d752015-12-18 04:35:24 -08002766 if (y == y3) {
2767 return 0;
2768 }
2769
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002770 // quickreject or quickaccept
2771 SkScalar min, max;
2772 find_minmax<4>(pts, &min, &max);
2773 if (x < min) {
2774 return 0;
2775 }
2776 if (x > max) {
2777 return dir;
2778 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002779
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002780 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002781 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002782 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002783 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002784 if (SkScalarNearlyEqual(xt, x)) {
2785 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2786 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002787 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002788 }
2789 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002790 return xt < x ? dir : 0;
2791}
2792
caryclark9aacd902015-12-14 08:38:09 -08002793static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002794 SkPoint dst[10];
2795 int n = SkChopCubicAtYExtrema(pts, dst);
2796 int w = 0;
2797 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002798 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 }
2800 return w;
2801}
2802
caryclark9aacd902015-12-14 08:38:09 -08002803static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2804 SkASSERT(src);
2805 SkASSERT(t >= 0 && t <= 1);
2806 SkScalar src2w = src[2] * w;
2807 SkScalar C = src[0];
2808 SkScalar A = src[4] - 2 * src2w + C;
2809 SkScalar B = 2 * (src2w - C);
2810 return (A * t + B) * t + C;
2811}
2812
2813
2814static double conic_eval_denominator(SkScalar w, SkScalar t) {
2815 SkScalar B = 2 * (w - 1);
2816 SkScalar C = 1;
2817 SkScalar A = -B;
2818 return (A * t + B) * t + C;
2819}
2820
2821static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2822 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002823 SkScalar y0 = pts[0].fY;
2824 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002825
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002826 int dir = 1;
2827 if (y0 > y2) {
2828 SkTSwap(y0, y2);
2829 dir = -1;
2830 }
caryclark9aacd902015-12-14 08:38:09 -08002831 if (y < y0 || y > y2) {
2832 return 0;
2833 }
caryclark9cb5d752015-12-18 04:35:24 -08002834 if (checkOnCurve(x, y, pts[0], pts[2])) {
2835 *onCurveCount += 1;
2836 return 0;
2837 }
caryclark9aacd902015-12-14 08:38:09 -08002838 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002839 return 0;
2840 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002841
caryclark9aacd902015-12-14 08:38:09 -08002842 SkScalar roots[2];
2843 SkScalar A = pts[2].fY;
2844 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2845 SkScalar C = pts[0].fY;
2846 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2847 B -= C; // B = b*w - w * yCept + yCept - a
2848 C -= y;
2849 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2850 SkASSERT(n <= 1);
2851 SkScalar xt;
2852 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002853 // zero roots are returned only when y0 == y
2854 // Need [0] if dir == 1
2855 // and [2] if dir == -1
2856 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002857 } else {
2858 SkScalar t = roots[0];
2859 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2860 }
2861 if (SkScalarNearlyEqual(xt, x)) {
2862 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2863 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002864 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002865 }
2866 }
2867 return xt < x ? dir : 0;
2868}
2869
2870static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2871 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2872 if (y0 == y1) {
2873 return true;
2874 }
2875 if (y0 < y1) {
2876 return y1 <= y2;
2877 } else {
2878 return y1 >= y2;
2879 }
2880}
2881
2882static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2883 int* onCurveCount) {
2884 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002885 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002886 // If the data points are very large, the conic may not be monotonic but may also
2887 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002888 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2889 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2890 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002891 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2892 }
2893 return w;
2894}
2895
2896static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2897 SkScalar y0 = pts[0].fY;
2898 SkScalar y2 = pts[2].fY;
2899
2900 int dir = 1;
2901 if (y0 > y2) {
2902 SkTSwap(y0, y2);
2903 dir = -1;
2904 }
2905 if (y < y0 || y > y2) {
2906 return 0;
2907 }
caryclark9cb5d752015-12-18 04:35:24 -08002908 if (checkOnCurve(x, y, pts[0], pts[2])) {
2909 *onCurveCount += 1;
2910 return 0;
2911 }
caryclark9aacd902015-12-14 08:38:09 -08002912 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002913 return 0;
2914 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002915 // bounds check on X (not required. is it faster?)
2916#if 0
2917 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2918 return 0;
2919 }
2920#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002921
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002922 SkScalar roots[2];
2923 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2924 2 * (pts[1].fY - pts[0].fY),
2925 pts[0].fY - y,
2926 roots);
2927 SkASSERT(n <= 1);
2928 SkScalar xt;
2929 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002930 // zero roots are returned only when y0 == y
2931 // Need [0] if dir == 1
2932 // and [2] if dir == -1
2933 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002934 } else {
2935 SkScalar t = roots[0];
2936 SkScalar C = pts[0].fX;
2937 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2938 SkScalar B = 2 * (pts[1].fX - C);
2939 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2940 }
caryclark9aacd902015-12-14 08:38:09 -08002941 if (SkScalarNearlyEqual(xt, x)) {
2942 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2943 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002944 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002945 }
2946 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002947 return xt < x ? dir : 0;
2948}
2949
caryclark9aacd902015-12-14 08:38:09 -08002950static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 SkPoint dst[5];
2952 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002953
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002954 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2955 n = SkChopQuadAtYExtrema(pts, dst);
2956 pts = dst;
2957 }
caryclark9aacd902015-12-14 08:38:09 -08002958 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002959 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002960 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002961 }
2962 return w;
2963}
2964
caryclark9aacd902015-12-14 08:38:09 -08002965static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002966 SkScalar x0 = pts[0].fX;
2967 SkScalar y0 = pts[0].fY;
2968 SkScalar x1 = pts[1].fX;
2969 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002970
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002972
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002973 int dir = 1;
2974 if (y0 > y1) {
2975 SkTSwap(y0, y1);
2976 dir = -1;
2977 }
caryclark9aacd902015-12-14 08:38:09 -08002978 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002979 return 0;
2980 }
caryclark9cb5d752015-12-18 04:35:24 -08002981 if (checkOnCurve(x, y, pts[0], pts[1])) {
2982 *onCurveCount += 1;
2983 return 0;
2984 }
2985 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002986 return 0;
2987 }
2988 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002989
caryclark9aacd902015-12-14 08:38:09 -08002990 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002991 // zero cross means the point is on the line, and since the case where
2992 // y of the query point is at the end point is handled above, we can be
2993 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002994 if (x != x1 || y != pts[1].fY) {
2995 *onCurveCount += 1;
2996 }
caryclark9aacd902015-12-14 08:38:09 -08002997 dir = 0;
2998 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 dir = 0;
3000 }
3001 return dir;
3002}
3003
caryclark9aacd902015-12-14 08:38:09 -08003004static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3005 SkTDArray<SkVector>* tangents) {
3006 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3007 && !between(pts[2].fY, y, pts[3].fY)) {
3008 return;
3009 }
3010 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3011 && !between(pts[2].fX, x, pts[3].fX)) {
3012 return;
3013 }
3014 SkPoint dst[10];
3015 int n = SkChopCubicAtYExtrema(pts, dst);
3016 for (int i = 0; i <= n; ++i) {
3017 SkPoint* c = &dst[i * 3];
3018 SkScalar t;
3019 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
3020 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3021 if (!SkScalarNearlyEqual(x, xt)) {
3022 continue;
3023 }
3024 SkVector tangent;
3025 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3026 tangents->push(tangent);
3027 }
3028}
3029
3030static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3031 SkTDArray<SkVector>* tangents) {
3032 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3033 return;
3034 }
3035 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3036 return;
3037 }
3038 SkScalar roots[2];
3039 SkScalar A = pts[2].fY;
3040 SkScalar B = pts[1].fY * w - y * w + y;
3041 SkScalar C = pts[0].fY;
3042 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3043 B -= C; // B = b*w - w * yCept + yCept - a
3044 C -= y;
3045 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3046 for (int index = 0; index < n; ++index) {
3047 SkScalar t = roots[index];
3048 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3049 if (!SkScalarNearlyEqual(x, xt)) {
3050 continue;
3051 }
3052 SkConic conic(pts, w);
3053 tangents->push(conic.evalTangentAt(t));
3054 }
3055}
3056
3057static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3058 SkTDArray<SkVector>* tangents) {
3059 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3060 return;
3061 }
3062 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3063 return;
3064 }
3065 SkScalar roots[2];
3066 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3067 2 * (pts[1].fY - pts[0].fY),
3068 pts[0].fY - y,
3069 roots);
3070 for (int index = 0; index < n; ++index) {
3071 SkScalar t = roots[index];
3072 SkScalar C = pts[0].fX;
3073 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3074 SkScalar B = 2 * (pts[1].fX - C);
3075 SkScalar xt = (A * t + B) * t + C;
3076 if (!SkScalarNearlyEqual(x, xt)) {
3077 continue;
3078 }
3079 tangents->push(SkEvalQuadTangentAt(pts, t));
3080 }
3081}
3082
3083static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3084 SkTDArray<SkVector>* tangents) {
3085 SkScalar y0 = pts[0].fY;
3086 SkScalar y1 = pts[1].fY;
3087 if (!between(y0, y, y1)) {
3088 return;
3089 }
3090 SkScalar x0 = pts[0].fX;
3091 SkScalar x1 = pts[1].fX;
3092 if (!between(x0, x, x1)) {
3093 return;
3094 }
3095 SkScalar dx = x1 - x0;
3096 SkScalar dy = y1 - y0;
3097 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3098 return;
3099 }
3100 SkVector v;
3101 v.set(dx, dy);
3102 tangents->push(v);
3103}
3104
reed@google.com4db592c2013-10-30 17:39:43 +00003105static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3106 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3107}
3108
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003109bool SkPath::contains(SkScalar x, SkScalar y) const {
3110 bool isInverse = this->isInverseFillType();
3111 if (this->isEmpty()) {
3112 return isInverse;
3113 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003114
reed@google.com4db592c2013-10-30 17:39:43 +00003115 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003116 return isInverse;
3117 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003118
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003119 SkPath::Iter iter(*this, true);
3120 bool done = false;
3121 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003122 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003123 do {
3124 SkPoint pts[4];
3125 switch (iter.next(pts, false)) {
3126 case SkPath::kMove_Verb:
3127 case SkPath::kClose_Verb:
3128 break;
3129 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003130 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003131 break;
3132 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003133 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003134 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003135 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003136 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003137 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003138 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003139 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003140 break;
3141 case SkPath::kDone_Verb:
3142 done = true;
3143 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003144 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003145 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003146 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3147 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3148 if (evenOddFill) {
3149 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003150 }
caryclark9aacd902015-12-14 08:38:09 -08003151 if (w) {
3152 return !isInverse;
3153 }
3154 if (onCurveCount <= 1) {
3155 return SkToBool(onCurveCount) ^ isInverse;
3156 }
3157 if ((onCurveCount & 1) || evenOddFill) {
3158 return SkToBool(onCurveCount & 1) ^ isInverse;
3159 }
3160 // If the point touches an even number of curves, and the fill is winding, check for
3161 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3162 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003163 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003164 SkTDArray<SkVector> tangents;
3165 do {
3166 SkPoint pts[4];
3167 int oldCount = tangents.count();
3168 switch (iter.next(pts, false)) {
3169 case SkPath::kMove_Verb:
3170 case SkPath::kClose_Verb:
3171 break;
3172 case SkPath::kLine_Verb:
3173 tangent_line(pts, x, y, &tangents);
3174 break;
3175 case SkPath::kQuad_Verb:
3176 tangent_quad(pts, x, y, &tangents);
3177 break;
3178 case SkPath::kConic_Verb:
3179 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3180 break;
3181 case SkPath::kCubic_Verb:
3182 tangent_cubic(pts, x, y, &tangents);
3183 break;
3184 case SkPath::kDone_Verb:
3185 done = true;
3186 break;
3187 }
3188 if (tangents.count() > oldCount) {
3189 int last = tangents.count() - 1;
3190 const SkVector& tangent = tangents[last];
3191 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3192 tangents.remove(last);
3193 } else {
3194 for (int index = 0; index < last; ++index) {
3195 const SkVector& test = tangents[index];
3196 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003197 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3198 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003199 tangents.remove(last);
3200 tangents.removeShuffle(index);
3201 break;
3202 }
3203 }
3204 }
3205 }
3206 } while (!done);
3207 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003208}
fmalitaaa0df4e2015-12-01 09:13:23 -08003209
3210int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3211 SkScalar w, SkPoint pts[], int pow2) {
3212 const SkConic conic(p0, p1, p2, w);
3213 return conic.chopIntoQuadsPOW2(pts, pow2);
3214}