blob: f3c2628c4fe33fe7449451de4225d4d1143e81ec [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
djsollen@google.com94e75ee2012-06-08 18:30:46 +00008#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -08009#include "SkCubicClipper.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000010#include "SkErrorInternals.h"
reed220f9262014-12-17 08:21:04 -080011#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070013#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
17////////////////////////////////////////////////////////////////////////////
18
reed@google.com3563c9e2011-11-14 19:34:57 +000019/**
20 * Path.bounds is defined to be the bounds of all the control points.
21 * If we called bounds.join(r) we would skip r if r was empty, which breaks
22 * our promise. Hence we have a custom joiner that doesn't look at emptiness
23 */
24static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
25 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
26 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
27 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
28 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
29}
30
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000031static bool is_degenerate(const SkPath& path) {
32 SkPath::Iter iter(path, false);
33 SkPoint pts[4];
34 return SkPath::kDone_Verb == iter.next(pts);
35}
36
bsalomon@google.com30c174b2012-11-13 14:36:42 +000037class SkAutoDisableDirectionCheck {
38public:
39 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070040 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000041 }
42
43 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070044 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000045 }
46
47private:
reed026beb52015-06-10 14:23:15 -070048 SkPath* fPath;
49 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000051#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000052
reed@android.com8a1c16f2008-12-17 15:59:43 +000053/* This guy's constructor/destructor bracket a path editing operation. It is
54 used when we know the bounds of the amount we are going to add to the path
55 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000056
reed@android.com8a1c16f2008-12-17 15:59:43 +000057 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000058 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000059 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000060
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000061 It also notes if the path was originally degenerate, and if so, sets
62 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000063 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 */
65class SkAutoPathBoundsUpdate {
66public:
67 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
68 this->init(path);
69 }
70
71 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
72 SkScalar right, SkScalar bottom) {
73 fRect.set(left, top, right, bottom);
74 this->init(path);
75 }
reed@google.comabf15c12011-01-18 20:35:51 +000076
reed@android.com8a1c16f2008-12-17 15:59:43 +000077 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000078 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
79 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 if (fEmpty || fHasValidBounds) {
81 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000082 }
83 }
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000086 SkPath* fPath;
87 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000088 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000090 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +000091
reed@android.com6b82d1a2009-06-03 02:35:01 +000092 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 // Cannot use fRect for our bounds unless we know it is sorted
94 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +000095 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +000096 // Mark the path's bounds as dirty if (1) they are, or (2) the path
97 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 if (fHasValidBounds && !fEmpty) {
101 joinNoEmptyChecks(&fRect, fPath->getBounds());
102 }
103 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000106#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108////////////////////////////////////////////////////////////////////////////
109
110/*
111 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000112 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
114
115 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000116 1. If we encounter degenerate segments, remove them
117 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
118 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
119 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120*/
121
122////////////////////////////////////////////////////////////////////////////
123
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000124// flag to require a moveTo if we begin with something else, like lineTo etc.
125#define INITIAL_LASTMOVETOINDEX_VALUE ~0
126
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000127SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800128 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000129 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700130 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000131}
132
133void SkPath::resetFields() {
134 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000135 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000136 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000137 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700138 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000139
140 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700141 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000142}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000143
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000145 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->copyFields(that);
147 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148}
149
150SkPath::~SkPath() {
151 SkDEBUGCODE(this->validate();)
152}
153
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154SkPath& SkPath::operator=(const SkPath& that) {
155 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 if (this != &that) {
158 fPathRef.reset(SkRef(that.fPathRef.get()));
159 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 }
161 SkDEBUGCODE(this->validate();)
162 return *this;
163}
164
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165void SkPath::copyFields(const SkPath& that) {
166 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000167 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000168 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700170 // Simulate fFirstDirection = that.fFirstDirection;
171 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700172 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173}
174
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000175bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000176 // note: don't need to look at isConvex or bounds, since just comparing the
177 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000178 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000179 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000180}
181
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700184 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700188 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
189 uint8_t temp = fFirstDirection;
190 fFirstDirection.store(that.fFirstDirection.load());
191 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700192 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000196static inline bool check_edge_against_rect(const SkPoint& p0,
197 const SkPoint& p1,
198 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700199 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000200 const SkPoint* edgeBegin;
201 SkVector v;
reed026beb52015-06-10 14:23:15 -0700202 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000203 v = p1 - p0;
204 edgeBegin = &p0;
205 } else {
206 v = p0 - p1;
207 edgeBegin = &p1;
208 }
209 if (v.fX || v.fY) {
210 // check the cross product of v with the vec from edgeBegin to each rect corner
211 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
212 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
213 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
214 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
215 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
216 return false;
217 }
218 }
219 return true;
220}
221
222bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
223 // This only handles non-degenerate convex paths currently.
224 if (kConvex_Convexity != this->getConvexity()) {
225 return false;
226 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000227
reed026beb52015-06-10 14:23:15 -0700228 SkPathPriv::FirstDirection direction;
229 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000230 return false;
231 }
232
233 SkPoint firstPt;
234 SkPoint prevPt;
235 RawIter iter(*this);
236 SkPath::Verb verb;
237 SkPoint pts[4];
238 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000239 SkDEBUGCODE(int segmentCount = 0;)
240 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000241
242 while ((verb = iter.next(pts)) != kDone_Verb) {
243 int nextPt = -1;
244 switch (verb) {
245 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000246 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000247 SkDEBUGCODE(++moveCnt);
248 firstPt = prevPt = pts[0];
249 break;
250 case kLine_Verb:
251 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000252 SkASSERT(moveCnt && !closeCount);
253 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254 break;
255 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000256 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000257 SkASSERT(moveCnt && !closeCount);
258 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000259 nextPt = 2;
260 break;
261 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000262 SkASSERT(moveCnt && !closeCount);
263 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000264 nextPt = 3;
265 break;
266 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000267 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268 break;
269 default:
270 SkDEBUGFAIL("unknown verb");
271 }
272 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800273 if (SkPath::kConic_Verb == verb) {
274 SkConic orig;
275 orig.set(pts, iter.conicWeight());
276 SkPoint quadPts[5];
277 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800278 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800279
280 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
281 return false;
282 }
283 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
284 return false;
285 }
286 } else {
287 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
288 return false;
289 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000290 }
291 prevPt = pts[nextPt];
292 }
293 }
294
295 return check_edge_against_rect(prevPt, firstPt, rect, direction);
296}
297
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000298uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000299 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800300#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000301 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
302 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
303#endif
304 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000305}
djsollen@google.come63793a2012-03-21 15:39:03 +0000306
reed@android.com8a1c16f2008-12-17 15:59:43 +0000307void SkPath::reset() {
308 SkDEBUGCODE(this->validate();)
309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000310 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000311 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312}
313
314void SkPath::rewind() {
315 SkDEBUGCODE(this->validate();)
316
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000317 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000318 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000319}
320
fsb1475b02016-01-20 09:51:07 -0800321bool SkPath::isLastContourClosed() const {
322 int verbCount = fPathRef->countVerbs();
323 if (0 == verbCount) {
324 return false;
325 }
326 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
327}
328
reed@google.com7e6c4d12012-05-10 14:05:43 +0000329bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000330 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000331
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000332 if (2 == verbCount) {
333 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
334 if (kLine_Verb == fPathRef->atVerb(1)) {
335 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000336 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000337 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000338 line[0] = pts[0];
339 line[1] = pts[1];
340 }
341 return true;
342 }
343 }
344 return false;
345}
346
caryclark@google.comf1316942011-07-26 19:54:45 +0000347/*
348 Determines if path is a rect by keeping track of changes in direction
349 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000350
caryclark@google.comf1316942011-07-26 19:54:45 +0000351 The direction is computed such that:
352 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000353 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000354 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000355 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000356
caryclark@google.comf1316942011-07-26 19:54:45 +0000357A rectangle cycles up/right/down/left or up/left/down/right.
358
359The test fails if:
360 The path is closed, and followed by a line.
361 A second move creates a new endpoint.
362 A diagonal line is parsed.
363 There's more than four changes of direction.
364 There's a discontinuity on the line (e.g., a move in the middle)
365 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000366 The path contains a quadratic or cubic.
367 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000368 *The rectangle doesn't complete a cycle.
369 *The final point isn't equal to the first point.
370
371 *These last two conditions we relax if we have a 3-edge path that would
372 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000373
caryclark@google.comf1316942011-07-26 19:54:45 +0000374It's OK if the path has:
375 Several colinear line segments composing a rectangle side.
376 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000377
caryclark@google.comf1316942011-07-26 19:54:45 +0000378The direction takes advantage of the corners found since opposite sides
379must travel in opposite directions.
380
381FIXME: Allow colinear quads and cubics to be treated like lines.
382FIXME: If the API passes fill-only, return true if the filled stroke
383 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000384
385 first,last,next direction state-machine:
386 0x1 is set if the segment is horizontal
387 0x2 is set if the segment is moving to the right or down
388 thus:
389 two directions are opposites iff (dirA ^ dirB) == 0x2
390 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000391
caryclark@google.comf1316942011-07-26 19:54:45 +0000392 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000393static int rect_make_dir(SkScalar dx, SkScalar dy) {
394 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
395}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000396bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
397 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000398 int corners = 0;
399 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000400 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700401 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000402 first.set(0, 0);
403 last.set(0, 0);
404 int firstDirection = 0;
405 int lastDirection = 0;
406 int nextDirection = 0;
407 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000408 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700409 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000410 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000411 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700412 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
413 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000415 savePts = pts;
416 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000417 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700418 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 case kLine_Verb: {
420 SkScalar left = last.fX;
421 SkScalar top = last.fY;
422 SkScalar right = pts->fX;
423 SkScalar bottom = pts->fY;
424 ++pts;
425 if (left != right && top != bottom) {
426 return false; // diagonal
427 }
428 if (left == right && top == bottom) {
429 break; // single point on side OK
430 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000431 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000432 if (0 == corners) {
433 firstDirection = nextDirection;
434 first = last;
435 last = pts[-1];
436 corners = 1;
437 closedOrMoved = false;
438 break;
439 }
440 if (closedOrMoved) {
441 return false; // closed followed by a line
442 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000443 if (autoClose && nextDirection == firstDirection) {
444 break; // colinear with first
445 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 closedOrMoved = autoClose;
447 if (lastDirection != nextDirection) {
448 if (++corners > 4) {
449 return false; // too many direction changes
450 }
451 }
452 last = pts[-1];
453 if (lastDirection == nextDirection) {
454 break; // colinear segment
455 }
456 // Possible values for corners are 2, 3, and 4.
457 // When corners == 3, nextDirection opposes firstDirection.
458 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000459 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
461 if ((directionCycle ^ turn) != nextDirection) {
462 return false; // direction didn't follow cycle
463 }
464 break;
465 }
466 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000467 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 case kCubic_Verb:
469 return false; // quadratic, cubic not allowed
470 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700471 if (allowPartial && !autoClose && firstDirection) {
472 insertClose = true;
473 *currVerb -= 1; // try move again afterwards
474 goto addMissingClose;
475 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 last = *pts++;
477 closedOrMoved = true;
478 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000479 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000480 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000481 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000483 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700485addMissingClose:
486 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 }
488 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000489 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000490 if (!result) {
491 // check if we are just an incomplete rectangle, in which case we can
492 // return true, but not claim to be closed.
493 // e.g.
494 // 3 sided rectangle
495 // 4 sided but the last edge is not long enough to reach the start
496 //
497 SkScalar closeX = first.x() - last.x();
498 SkScalar closeY = first.y() - last.y();
499 if (closeX && closeY) {
500 return false; // we're diagonal, abort (can we ever reach this?)
501 }
502 int closeDirection = rect_make_dir(closeX, closeY);
503 // make sure the close-segment doesn't double-back on itself
504 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
505 result = true;
506 autoClose = false; // we are not closed
507 }
508 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000509 if (savePts) {
510 *ptsPtr = savePts;
511 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000512 if (result && isClosed) {
513 *isClosed = autoClose;
514 }
515 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000516 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000517 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000518 return result;
519}
520
robertphillips4f662e62014-12-29 14:06:51 -0800521bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000522 SkDEBUGCODE(this->validate();)
523 int currVerb = 0;
524 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800525 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800526 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800527 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000528 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800529 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800530 int32_t num = SkToS32(pts - first);
531 if (num) {
532 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800533 } else {
534 // 'pts' isn't updated for open rects
535 *rect = this->getBounds();
536 }
537 }
538 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000539}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000540
caryclark95bc5f32015-04-08 08:34:15 -0700541bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000542 SkDEBUGCODE(this->validate();)
543 int currVerb = 0;
544 const SkPoint* pts = fPathRef->points();
545 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000546 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700547 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return false;
549 }
550 const SkPoint* last = pts;
551 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700552 bool isClosed;
553 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000554 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700555 if (!isClosed) {
556 pts = fPathRef->points() + fPathRef->countPoints();
557 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000558 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 if (testRects[0].contains(testRects[1])) {
560 if (rects) {
561 rects[0] = testRects[0];
562 rects[1] = testRects[1];
563 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000564 if (dirs) {
565 dirs[0] = testDirs[0];
566 dirs[1] = testDirs[1];
567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 return true;
569 }
570 if (testRects[1].contains(testRects[0])) {
571 if (rects) {
572 rects[0] = testRects[1];
573 rects[1] = testRects[0];
574 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000575 if (dirs) {
576 dirs[0] = testDirs[1];
577 dirs[1] = testDirs[0];
578 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000579 return true;
580 }
581 }
582 return false;
583}
584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000585int SkPath::countPoints() const {
586 return fPathRef->countPoints();
587}
588
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000589int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000590 SkDEBUGCODE(this->validate();)
591
592 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000593 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000594 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800595 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000596 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000597}
598
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000599SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000600 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
601 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000602 }
603 return SkPoint::Make(0, 0);
604}
605
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000606int SkPath::countVerbs() const {
607 return fPathRef->countVerbs();
608}
609
610static inline void copy_verbs_reverse(uint8_t* inorderDst,
611 const uint8_t* reversedSrc,
612 int count) {
613 for (int i = 0; i < count; ++i) {
614 inorderDst[i] = reversedSrc[~i];
615 }
616}
617
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000618int SkPath::getVerbs(uint8_t dst[], int max) const {
619 SkDEBUGCODE(this->validate();)
620
621 SkASSERT(max >= 0);
622 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 int count = SkMin32(max, fPathRef->countVerbs());
624 copy_verbs_reverse(dst, fPathRef->verbs(), count);
625 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000626}
627
reed@google.com294dd7b2011-10-11 11:58:32 +0000628bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000629 SkDEBUGCODE(this->validate();)
630
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000631 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000632 if (count > 0) {
633 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000635 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000636 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000638 if (lastPt) {
639 lastPt->set(0, 0);
640 }
641 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000642}
643
caryclarkaec25102015-04-29 08:28:30 -0700644void SkPath::setPt(int index, SkScalar x, SkScalar y) {
645 SkDEBUGCODE(this->validate();)
646
647 int count = fPathRef->countPoints();
648 if (count <= index) {
649 return;
650 } else {
651 SkPathRef::Editor ed(&fPathRef);
652 ed.atPoint(index)->set(x, y);
653 }
654}
655
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656void SkPath::setLastPt(SkScalar x, SkScalar y) {
657 SkDEBUGCODE(this->validate();)
658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660 if (count == 0) {
661 this->moveTo(x, y);
662 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 SkPathRef::Editor ed(&fPathRef);
664 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665 }
666}
667
reed@google.com04863fa2011-05-15 04:08:24 +0000668void SkPath::setConvexity(Convexity c) {
669 if (fConvexity != c) {
670 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000671 }
672}
673
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674//////////////////////////////////////////////////////////////////////////////
675// Construction methods
676
reed026beb52015-06-10 14:23:15 -0700677#define DIRTY_AFTER_EDIT \
678 do { \
679 fConvexity = kUnknown_Convexity; \
680 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000681 } while (0)
682
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683void SkPath::incReserve(U16CPU inc) {
684 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000685 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkDEBUGCODE(this->validate();)
687}
688
689void SkPath::moveTo(SkScalar x, SkScalar y) {
690 SkDEBUGCODE(this->validate();)
691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000694 // remember our index
695 fLastMoveToIndex = fPathRef->countPoints();
696
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000697 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700698
699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rMoveTo(SkScalar x, SkScalar y) {
703 SkPoint pt;
704 this->getLastPt(&pt);
705 this->moveTo(pt.fX + x, pt.fY + y);
706}
707
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000708void SkPath::injectMoveToIfNeeded() {
709 if (fLastMoveToIndex < 0) {
710 SkScalar x, y;
711 if (fPathRef->countVerbs() == 0) {
712 x = y = 0;
713 } else {
714 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
715 x = pt.fX;
716 y = pt.fY;
717 }
718 this->moveTo(x, y);
719 }
720}
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722void SkPath::lineTo(SkScalar x, SkScalar y) {
723 SkDEBUGCODE(this->validate();)
724
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000725 this->injectMoveToIfNeeded();
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 SkPathRef::Editor ed(&fPathRef);
728 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729
reed@google.comb54455e2011-05-16 14:16:04 +0000730 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731}
732
733void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000734 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000735 SkPoint pt;
736 this->getLastPt(&pt);
737 this->lineTo(pt.fX + x, pt.fY + y);
738}
739
740void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000743 this->injectMoveToIfNeeded();
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000749
reed@google.comb54455e2011-05-16 14:16:04 +0000750 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751}
752
753void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000754 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755 SkPoint pt;
756 this->getLastPt(&pt);
757 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
758}
759
reed@google.com277c3f82013-05-31 15:17:50 +0000760void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
761 SkScalar w) {
762 // check for <= 0 or NaN with this test
763 if (!(w > 0)) {
764 this->lineTo(x2, y2);
765 } else if (!SkScalarIsFinite(w)) {
766 this->lineTo(x1, y1);
767 this->lineTo(x2, y2);
768 } else if (SK_Scalar1 == w) {
769 this->quadTo(x1, y1, x2, y2);
770 } else {
771 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 this->injectMoveToIfNeeded();
774
reed@google.com277c3f82013-05-31 15:17:50 +0000775 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000776 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000777 pts[0].set(x1, y1);
778 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000779
reed@google.com277c3f82013-05-31 15:17:50 +0000780 DIRTY_AFTER_EDIT;
781 }
782}
783
784void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
785 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000786 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000787 SkPoint pt;
788 this->getLastPt(&pt);
789 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
790}
791
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
793 SkScalar x3, SkScalar y3) {
794 SkDEBUGCODE(this->validate();)
795
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000796 this->injectMoveToIfNeeded();
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 SkPathRef::Editor ed(&fPathRef);
799 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 pts[0].set(x1, y1);
801 pts[1].set(x2, y2);
802 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803
reed@google.comb54455e2011-05-16 14:16:04 +0000804 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805}
806
807void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
808 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000809 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810 SkPoint pt;
811 this->getLastPt(&pt);
812 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
813 pt.fX + x3, pt.fY + y3);
814}
815
816void SkPath::close() {
817 SkDEBUGCODE(this->validate();)
818
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000819 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000821 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 case kLine_Verb:
823 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000824 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000826 case kMove_Verb: {
827 SkPathRef::Editor ed(&fPathRef);
828 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000830 }
reed@google.com277c3f82013-05-31 15:17:50 +0000831 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000832 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000833 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000834 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000835 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000836 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 }
838 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839
840 // signal that we need a moveTo to follow us (unless we're done)
841#if 0
842 if (fLastMoveToIndex >= 0) {
843 fLastMoveToIndex = ~fLastMoveToIndex;
844 }
845#else
846 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
847#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848}
849
850///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000851
fmalitac08d53e2015-11-17 09:53:29 -0800852namespace {
853
854template <unsigned N>
855class PointIterator {
856public:
857 PointIterator(SkPath::Direction dir, unsigned startIndex)
858 : fCurrent(startIndex % N)
859 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
860
861 const SkPoint& current() const {
862 SkASSERT(fCurrent < N);
863 return fPts[fCurrent];
864 }
865
866 const SkPoint& next() {
867 fCurrent = (fCurrent + fAdvance) % N;
868 return this->current();
869 }
870
871protected:
872 SkPoint fPts[N];
873
874private:
875 unsigned fCurrent;
876 unsigned fAdvance;
877};
878
879class RectPointIterator : public PointIterator<4> {
880public:
881 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
882 : PointIterator(dir, startIndex) {
883
884 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
885 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
886 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
887 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
888 }
889};
890
891class OvalPointIterator : public PointIterator<4> {
892public:
893 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
894 : PointIterator(dir, startIndex) {
895
896 const SkScalar cx = oval.centerX();
897 const SkScalar cy = oval.centerY();
898
899 fPts[0] = SkPoint::Make(cx, oval.fTop);
900 fPts[1] = SkPoint::Make(oval.fRight, cy);
901 fPts[2] = SkPoint::Make(cx, oval.fBottom);
902 fPts[3] = SkPoint::Make(oval.fLeft, cy);
903 }
904};
905
906class RRectPointIterator : public PointIterator<8> {
907public:
908 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
909 : PointIterator(dir, startIndex) {
910
911 const SkRect& bounds = rrect.getBounds();
912 const SkScalar L = bounds.fLeft;
913 const SkScalar T = bounds.fTop;
914 const SkScalar R = bounds.fRight;
915 const SkScalar B = bounds.fBottom;
916
917 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
918 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
919 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
920 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
921 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
922 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
923 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
924 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
925 }
926};
927
928} // anonymous namespace
929
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000930static void assert_known_direction(int dir) {
931 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
932}
933
reed@android.com8a1c16f2008-12-17 15:59:43 +0000934void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800935 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000936}
937
938void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
939 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800940 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
941}
942
943void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000944 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700945 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800946 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000947 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800948 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000949
fmalitac08d53e2015-11-17 09:53:29 -0800950 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000951
fmalitac08d53e2015-11-17 09:53:29 -0800952 const int kVerbs = 5; // moveTo + 3x lineTo + close
953 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954
fmalitac08d53e2015-11-17 09:53:29 -0800955 RectPointIterator iter(rect, dir, startIndex);
956
957 this->moveTo(iter.current());
958 this->lineTo(iter.next());
959 this->lineTo(iter.next());
960 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000961 this->close();
fmalitac08d53e2015-11-17 09:53:29 -0800962
963 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000964}
965
reed@google.com744faba2012-05-29 19:54:52 +0000966void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
967 SkDEBUGCODE(this->validate();)
968 if (count <= 0) {
969 return;
970 }
971
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000972 fLastMoveToIndex = fPathRef->countPoints();
973
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000974 // +close makes room for the extra kClose_Verb
975 SkPathRef::Editor ed(&fPathRef, count+close, count);
976
977 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +0000978 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000979 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
980 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +0000981 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000982
reed@google.com744faba2012-05-29 19:54:52 +0000983 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000984 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -0800985 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000986 }
987
reed@google.com744faba2012-05-29 19:54:52 +0000988 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000989 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000990}
991
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000992#include "SkGeometry.h"
993
reedf90ea012015-01-29 12:03:58 -0800994static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
995 SkPoint* pt) {
996 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000997 // Chrome uses this path to move into and out of ovals. If not
998 // treated as a special case the moves can distort the oval's
999 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001000 pt->set(oval.fRight, oval.centerY());
1001 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001002 } else if (0 == oval.width() && 0 == oval.height()) {
1003 // Chrome will sometimes create 0 radius round rects. Having degenerate
1004 // quad segments in the path prevents the path from being recognized as
1005 // a rect.
1006 // TODO: optimizing the case where only one of width or height is zero
1007 // should also be considered. This case, however, doesn't seem to be
1008 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001009 pt->set(oval.fRight, oval.fTop);
1010 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001011 }
reedf90ea012015-01-29 12:03:58 -08001012 return false;
1013}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001014
reedd5d27d92015-02-09 13:54:43 -08001015// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1016//
1017static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1018 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1019 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1020 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001021
1022 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001023 loss in radians-conversion and/or sin/cos, we may end up with coincident
1024 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1025 of drawing a nearly complete circle (good).
1026 e.g. canvas.drawArc(0, 359.99, ...)
1027 -vs- canvas.drawArc(0, 359.9, ...)
1028 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001029 */
reedd5d27d92015-02-09 13:54:43 -08001030 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001031 SkScalar sw = SkScalarAbs(sweepAngle);
1032 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1033 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1034 // make a guess at a tiny angle (in radians) to tweak by
1035 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1036 // not sure how much will be enough, so we use a loop
1037 do {
1038 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001039 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1040 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 }
1042 }
reedd5d27d92015-02-09 13:54:43 -08001043 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1044}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045
reed9e779d42015-02-17 11:43:14 -08001046/**
1047 * If this returns 0, then the caller should just line-to the singlePt, else it should
1048 * ignore singlePt and append the specified number of conics.
1049 */
reedd5d27d92015-02-09 13:54:43 -08001050static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001051 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1052 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001053 SkMatrix matrix;
1054
1055 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1056 matrix.postTranslate(oval.centerX(), oval.centerY());
1057
reed9e779d42015-02-17 11:43:14 -08001058 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1059 if (0 == count) {
1060 matrix.mapXY(start.x(), start.y(), singlePt);
1061 }
1062 return count;
reedd5d27d92015-02-09 13:54:43 -08001063}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001065void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001067 SkRRect rrect;
1068 rrect.setRectRadii(rect, (const SkVector*) radii);
1069 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001070}
1071
reed@google.com4ed0fb72012-12-12 20:48:18 +00001072void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001073 // legacy start indices: 6 (CW) and 7(CCW)
1074 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1075}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001076
fmalitac08d53e2015-11-17 09:53:29 -08001077void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1078 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001079
fmalitac08d53e2015-11-17 09:53:29 -08001080 if (rrect.isEmpty()) {
1081 return;
reed1b28a3a2014-12-17 14:42:09 -08001082 }
fmalitac08d53e2015-11-17 09:53:29 -08001083
caryclarkda707bf2015-11-19 14:47:43 -08001084 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001085 const SkRect& bounds = rrect.getBounds();
1086
1087 if (rrect.isRect()) {
1088 // degenerate(rect) => radii points are collapsing
1089 this->addRect(bounds, dir, (startIndex + 1) / 2);
1090 } else if (rrect.isOval()) {
1091 // degenerate(oval) => line points are collapsing
1092 this->addOval(bounds, dir, startIndex / 2);
1093 } else {
1094 fFirstDirection = this->hasOnlyMoveTos() ?
1095 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1096
1097 SkAutoPathBoundsUpdate apbu(this, bounds);
1098 SkAutoDisableDirectionCheck addc(this);
1099
1100 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1101 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1102 const SkScalar weight = SK_ScalarRoot2Over2;
1103
1104 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1105 const int kVerbs = startsWithConic
1106 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1107 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1108 this->incReserve(kVerbs);
1109
1110 RRectPointIterator rrectIter(rrect, dir, startIndex);
1111 // Corner iterator indices follow the collapsed radii model,
1112 // adjusted such that the start pt is "behind" the radii start pt.
1113 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1114 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1115
1116 this->moveTo(rrectIter.current());
1117 if (startsWithConic) {
1118 for (unsigned i = 0; i < 3; ++i) {
1119 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1120 this->lineTo(rrectIter.next());
1121 }
1122 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1123 // final lineTo handled by close().
1124 } else {
1125 for (unsigned i = 0; i < 4; ++i) {
1126 this->lineTo(rrectIter.next());
1127 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1128 }
1129 }
1130 this->close();
1131
caryclarkda707bf2015-11-19 14:47:43 -08001132 SkPathRef::Editor ed(&fPathRef);
1133 ed.setIsRRect(isRRect);
1134
fmalitac08d53e2015-11-17 09:53:29 -08001135 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1136 }
1137
1138 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001139}
1140
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001141bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001142 int count = fPathRef->countVerbs();
1143 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1144 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001145 if (*verbs == kLine_Verb ||
1146 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001147 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001148 *verbs == kCubic_Verb) {
1149 return false;
1150 }
1151 ++verbs;
1152 }
1153 return true;
1154}
1155
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001156void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1157 Direction dir) {
1158 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001159
humper@google.com75e3ca12013-04-08 21:44:11 +00001160 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001161 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001162 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001163 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001164 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1165 return;
1166 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001167
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001168 SkRRect rrect;
1169 rrect.setRectXY(rect, rx, ry);
1170 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001171}
1172
reed@android.com8a1c16f2008-12-17 15:59:43 +00001173void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001174 // legacy start index: 1
1175 this->addOval(oval, dir, 1);
1176}
1177
1178void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001179 assert_known_direction(dir);
1180
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181 /* If addOval() is called after previous moveTo(),
1182 this path is still marked as an oval. This is used to
1183 fit into WebKit's calling sequences.
1184 We can't simply check isEmpty() in this case, as additional
1185 moveTo() would mark the path non empty.
1186 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001187 bool isOval = hasOnlyMoveTos();
1188 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001189 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001190 } else {
reed026beb52015-06-10 14:23:15 -07001191 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001192 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001193
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001194 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001195 SkAutoPathBoundsUpdate apbu(this, oval);
1196
fmalitac08d53e2015-11-17 09:53:29 -08001197 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1198 const int kVerbs = 6; // moveTo + 4x conicTo + close
1199 this->incReserve(kVerbs);
1200
1201 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1202 // The corner iterator pts are tracking "behind" the oval/radii pts.
1203 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001204 const SkScalar weight = SK_ScalarRoot2Over2;
1205
fmalitac08d53e2015-11-17 09:53:29 -08001206 this->moveTo(ovalIter.current());
1207 for (unsigned i = 0; i < 4; ++i) {
1208 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001209 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001210 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001211
fmalitac08d53e2015-11-17 09:53:29 -08001212 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1213
robertphillips@google.com466310d2013-12-03 16:43:54 +00001214 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001215
robertphillips@google.com466310d2013-12-03 16:43:54 +00001216 ed.setIsOval(isOval);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001217}
1218
reed@android.com8a1c16f2008-12-17 15:59:43 +00001219void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1220 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001221 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001222 }
1223}
1224
reed@android.com8a1c16f2008-12-17 15:59:43 +00001225void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1226 bool forceMoveTo) {
1227 if (oval.width() < 0 || oval.height() < 0) {
1228 return;
1229 }
1230
reedf90ea012015-01-29 12:03:58 -08001231 if (fPathRef->countVerbs() == 0) {
1232 forceMoveTo = true;
1233 }
1234
1235 SkPoint lonePt;
1236 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1237 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1238 return;
1239 }
1240
reedd5d27d92015-02-09 13:54:43 -08001241 SkVector startV, stopV;
1242 SkRotationDirection dir;
1243 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1244
reed9e779d42015-02-17 11:43:14 -08001245 SkPoint singlePt;
reedd5d27d92015-02-09 13:54:43 -08001246 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001247 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001248 if (count) {
1249 this->incReserve(count * 2 + 1);
1250 const SkPoint& pt = conics[0].fPts[0];
1251 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1252 for (int i = 0; i < count; ++i) {
1253 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1254 }
reed9e779d42015-02-17 11:43:14 -08001255 } else {
1256 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001257 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001258}
1259
caryclark55d49052016-01-23 05:07:04 -08001260// This converts the SVG arc to conics.
1261// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1262// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1263// See also SVG implementation notes:
1264// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1265// Note that arcSweep bool value is flipped from the original implementation.
1266void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1267 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
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 after.setLength(dist);
1418 this->lineTo(xx, yy);
1419 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1420 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421}
1422
1423///////////////////////////////////////////////////////////////////////////////
1424
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001425void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001426 SkMatrix matrix;
1427
1428 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001429 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430}
1431
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001432void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001433 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001435 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 SkPoint pts[4];
1437 Verb verb;
1438
1439 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001440 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441 while ((verb = iter.next(pts)) != kDone_Verb) {
1442 switch (verb) {
1443 case kMove_Verb:
1444 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001445 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1446 injectMoveToIfNeeded(); // In case last contour is closed
1447 this->lineTo(pts[0]);
1448 } else {
1449 this->moveTo(pts[0]);
1450 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001451 break;
1452 case kLine_Verb:
1453 proc(matrix, &pts[1], &pts[1], 1);
1454 this->lineTo(pts[1]);
1455 break;
1456 case kQuad_Verb:
1457 proc(matrix, &pts[1], &pts[1], 2);
1458 this->quadTo(pts[1], pts[2]);
1459 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001460 case kConic_Verb:
1461 proc(matrix, &pts[1], &pts[1], 2);
1462 this->conicTo(pts[1], pts[2], iter.conicWeight());
1463 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 case kCubic_Verb:
1465 proc(matrix, &pts[1], &pts[1], 3);
1466 this->cubicTo(pts[1], pts[2], pts[3]);
1467 break;
1468 case kClose_Verb:
1469 this->close();
1470 break;
1471 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001472 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001474 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475 }
1476}
1477
1478///////////////////////////////////////////////////////////////////////////////
1479
reed@google.com277c3f82013-05-31 15:17:50 +00001480static int pts_in_verb(unsigned verb) {
1481 static const uint8_t gPtsInVerb[] = {
1482 1, // kMove
1483 1, // kLine
1484 2, // kQuad
1485 2, // kConic
1486 3, // kCubic
1487 0, // kClose
1488 0 // kDone
1489 };
1490
1491 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1492 return gPtsInVerb[verb];
1493}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495// ignore the last point of the 1st contour
1496void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001497 int i, vcount = path.fPathRef->countVerbs();
1498 // exit early if the path is empty, or just has a moveTo.
1499 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 return;
1501 }
1502
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001503 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001505 const uint8_t* verbs = path.fPathRef->verbs();
1506 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001507 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001508
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001509 SkASSERT(verbs[~0] == kMove_Verb);
1510 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001511 unsigned v = verbs[~i];
1512 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 if (n == 0) {
1514 break;
1515 }
1516 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001517 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 }
1519
1520 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001521 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 case kLine_Verb:
1523 this->lineTo(pts[-1].fX, pts[-1].fY);
1524 break;
1525 case kQuad_Verb:
1526 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1527 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001528 case kConic_Verb:
1529 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1530 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 case kCubic_Verb:
1532 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1533 pts[-3].fX, pts[-3].fY);
1534 break;
1535 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001536 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 break;
1538 }
reed@google.com277c3f82013-05-31 15:17:50 +00001539 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 }
1541}
1542
reed@google.com63d73742012-01-10 15:33:12 +00001543void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001544 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001545
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001546 const SkPoint* pts = src.fPathRef->pointsEnd();
1547 // we will iterator through src's verbs backwards
1548 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1549 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001550 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001551
1552 bool needMove = true;
1553 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001554 while (verbs < verbsEnd) {
1555 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001556 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001557
1558 if (needMove) {
1559 --pts;
1560 this->moveTo(pts->fX, pts->fY);
1561 needMove = false;
1562 }
1563 pts -= n;
1564 switch (v) {
1565 case kMove_Verb:
1566 if (needClose) {
1567 this->close();
1568 needClose = false;
1569 }
1570 needMove = true;
1571 pts += 1; // so we see the point in "if (needMove)" above
1572 break;
1573 case kLine_Verb:
1574 this->lineTo(pts[0]);
1575 break;
1576 case kQuad_Verb:
1577 this->quadTo(pts[1], pts[0]);
1578 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001579 case kConic_Verb:
1580 this->conicTo(pts[1], pts[0], *--conicWeights);
1581 break;
reed@google.com63d73742012-01-10 15:33:12 +00001582 case kCubic_Verb:
1583 this->cubicTo(pts[2], pts[1], pts[0]);
1584 break;
1585 case kClose_Verb:
1586 needClose = true;
1587 break;
1588 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001589 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001590 }
1591 }
1592}
1593
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594///////////////////////////////////////////////////////////////////////////////
1595
1596void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1597 SkMatrix matrix;
1598
1599 matrix.setTranslate(dx, dy);
1600 this->transform(matrix, dst);
1601}
1602
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1604 int level = 2) {
1605 if (--level >= 0) {
1606 SkPoint tmp[7];
1607
1608 SkChopCubicAtHalf(pts, tmp);
1609 subdivide_cubic_to(path, &tmp[0], level);
1610 subdivide_cubic_to(path, &tmp[3], level);
1611 } else {
1612 path->cubicTo(pts[1], pts[2], pts[3]);
1613 }
1614}
1615
1616void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1617 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001618 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 dst = (SkPath*)this;
1620 }
1621
tomhudson@google.com8d430182011-06-06 19:11:19 +00001622 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 SkPath tmp;
1624 tmp.fFillType = fFillType;
1625
1626 SkPath::Iter iter(*this, false);
1627 SkPoint pts[4];
1628 SkPath::Verb verb;
1629
reed@google.com4a3b7142012-05-16 17:16:46 +00001630 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 switch (verb) {
1632 case kMove_Verb:
1633 tmp.moveTo(pts[0]);
1634 break;
1635 case kLine_Verb:
1636 tmp.lineTo(pts[1]);
1637 break;
1638 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001639 // promote the quad to a conic
1640 tmp.conicTo(pts[1], pts[2],
1641 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001643 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001644 tmp.conicTo(pts[1], pts[2],
1645 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001646 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 case kCubic_Verb:
1648 subdivide_cubic_to(&tmp, pts);
1649 break;
1650 case kClose_Verb:
1651 tmp.close();
1652 break;
1653 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001654 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 break;
1656 }
1657 }
1658
1659 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 SkPathRef::Editor ed(&dst->fPathRef);
1661 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001662 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001664 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1665
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001667 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001668 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001669 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001671
reed026beb52015-06-10 14:23:15 -07001672 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1673 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001674 } else {
1675 SkScalar det2x2 =
1676 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1677 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1678 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001679 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1680 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001681 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001682 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001683 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001684 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001685 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001686 }
1687 }
1688
reed@android.com8a1c16f2008-12-17 15:59:43 +00001689 SkDEBUGCODE(dst->validate();)
1690 }
1691}
1692
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693///////////////////////////////////////////////////////////////////////////////
1694///////////////////////////////////////////////////////////////////////////////
1695
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001696enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001697 kEmptyContour_SegmentState, // The current contour is empty. We may be
1698 // starting processing or we may have just
1699 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001700 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1701 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1702 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703};
1704
1705SkPath::Iter::Iter() {
1706#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001707 fPts = nullptr;
1708 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001710 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001711 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712#endif
1713 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001714 fVerbs = nullptr;
1715 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 fNeedClose = false;
1717}
1718
1719SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1720 this->setPath(path, forceClose);
1721}
1722
1723void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001724 fPts = path.fPathRef->points();
1725 fVerbs = path.fPathRef->verbs();
1726 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001727 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001728 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001729 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730 fForceClose = SkToU8(forceClose);
1731 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001732 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733}
1734
1735bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001736 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 return false;
1738 }
1739 if (fForceClose) {
1740 return true;
1741 }
1742
1743 const uint8_t* verbs = fVerbs;
1744 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001745
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001746 if (kMove_Verb == *(verbs - 1)) {
1747 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 }
1749
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001750 while (verbs > stop) {
1751 // verbs points one beyond the current verb, decrement first.
1752 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 if (kMove_Verb == v) {
1754 break;
1755 }
1756 if (kClose_Verb == v) {
1757 return true;
1758 }
1759 }
1760 return false;
1761}
1762
1763SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001764 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001766 // A special case: if both points are NaN, SkPoint::operation== returns
1767 // false, but the iterator expects that they are treated as the same.
1768 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001769 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1770 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001771 return kClose_Verb;
1772 }
1773
reed@google.com9e25dbf2012-05-15 17:05:38 +00001774 pts[0] = fLastPt;
1775 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 fLastPt = fMoveTo;
1777 fCloseLine = true;
1778 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001779 } else {
1780 pts[0] = fMoveTo;
1781 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783}
1784
reed@google.com9e25dbf2012-05-15 17:05:38 +00001785const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001786 if (fSegmentState == kAfterMove_SegmentState) {
1787 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001789 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001791 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1792 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001793 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795}
1796
caryclarke8c56662015-07-14 11:19:26 -07001797void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001798 // We need to step over anything that will not move the current draw point
1799 // forward before the next move is seen
1800 const uint8_t* lastMoveVerb = 0;
1801 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001802 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001803 SkPoint lastPt = fLastPt;
1804 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001805 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 switch (verb) {
1807 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001808 // Keep a record of this most recent move
1809 lastMoveVerb = fVerbs;
1810 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001811 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001812 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001813 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 fPts++;
1815 break;
1816
1817 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001818 // A close when we are in a segment is always valid except when it
1819 // follows a move which follows a segment.
1820 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001821 return;
1822 }
1823 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001824 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 break;
1826
1827 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001828 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829 if (lastMoveVerb) {
1830 fVerbs = lastMoveVerb;
1831 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001832 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 return;
1834 }
1835 return;
1836 }
1837 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001838 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 fPts++;
1840 break;
1841
reed@google.com277c3f82013-05-31 15:17:50 +00001842 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001843 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001844 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 if (lastMoveVerb) {
1846 fVerbs = lastMoveVerb;
1847 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001848 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001849 return;
1850 }
1851 return;
1852 }
1853 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001854 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001856 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 break;
1858
1859 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001860 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 if (lastMoveVerb) {
1862 fVerbs = lastMoveVerb;
1863 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001864 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 return;
1866 }
1867 return;
1868 }
1869 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001870 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 fPts += 3;
1872 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001873
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001875 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 }
1877 }
1878}
1879
reed@google.com4a3b7142012-05-16 17:16:46 +00001880SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001881 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001882
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 // Close the curve if requested and if there is some curve to close
1885 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001886 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887 return kLine_Verb;
1888 }
1889 fNeedClose = false;
1890 return kClose_Verb;
1891 }
1892 return kDone_Verb;
1893 }
1894
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001895 // fVerbs is one beyond the current verb, decrement first
1896 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001897 const SkPoint* SK_RESTRICT srcPts = fPts;
1898 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899
1900 switch (verb) {
1901 case kMove_Verb:
1902 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001903 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 verb = this->autoClose(pts);
1905 if (verb == kClose_Verb) {
1906 fNeedClose = false;
1907 }
1908 return (Verb)verb;
1909 }
1910 if (fVerbs == fVerbStop) { // might be a trailing moveto
1911 return kDone_Verb;
1912 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001913 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001914 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001916 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 fNeedClose = fForceClose;
1919 break;
1920 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001921 pts[0] = this->cons_moveTo();
1922 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 fLastPt = srcPts[0];
1924 fCloseLine = false;
1925 srcPts += 1;
1926 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001927 case kConic_Verb:
1928 fConicWeights += 1;
1929 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001930 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001931 pts[0] = this->cons_moveTo();
1932 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 fLastPt = srcPts[1];
1934 srcPts += 2;
1935 break;
1936 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001937 pts[0] = this->cons_moveTo();
1938 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 fLastPt = srcPts[2];
1940 srcPts += 3;
1941 break;
1942 case kClose_Verb:
1943 verb = this->autoClose(pts);
1944 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001945 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 } else {
1947 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001948 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 break;
1952 }
1953 fPts = srcPts;
1954 return (Verb)verb;
1955}
1956
1957///////////////////////////////////////////////////////////////////////////////
1958
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001960 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961*/
1962
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001963size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 SkDEBUGCODE(this->validate();)
1965
halcanary96fcdcc2015-08-27 07:41:13 -07001966 if (nullptr == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00001967 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001968 return SkAlign4(byteCount);
1969 }
1970
1971 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001972
robertphillips@google.com466310d2013-12-03 16:43:54 +00001973 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001974 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07001975 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07001976 (fIsVolatile << kIsVolatile_SerializationShift) |
1977 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001978
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001979 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001980
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001981 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001982
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001983 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001984 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001985}
1986
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00001987size_t SkPath::readFromMemory(const void* storage, size_t length) {
1988 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001989
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00001990 int32_t packed;
1991 if (!buffer.readS32(&packed)) {
1992 return 0;
1993 }
1994
reed8f086022015-06-11 14:22:19 -07001995 unsigned version = packed & 0xFF;
mtklein1b249332015-07-07 12:21:21 -07001996
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001997 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
1998 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed8f086022015-06-11 14:22:19 -07001999 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002000 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002001 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002002 if (!pathRef) {
2003 return 0;
2004 }
2005
2006 fPathRef.reset(pathRef);
2007 SkDEBUGCODE(this->validate();)
2008 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002009
reed8f086022015-06-11 14:22:19 -07002010 // compatibility check
2011 if (version < kPathPrivFirstDirection_Version) {
2012 switch (dir) { // old values
2013 case 0:
2014 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2015 break;
2016 case 1:
2017 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2018 break;
2019 case 2:
2020 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2021 break;
2022 default:
2023 SkASSERT(false);
2024 }
2025 } else {
2026 fFirstDirection = dir;
2027 }
2028
ajumaf8aec582016-01-13 13:46:31 -08002029 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030}
2031
2032///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033
reede05fed02014-12-15 07:59:53 -08002034#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002035#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002036
reed@google.com51bbe752013-01-17 22:07:50 +00002037static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002038 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002039 str->append(label);
2040 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002041
reed@google.com51bbe752013-01-17 22:07:50 +00002042 const SkScalar* values = &pts[0].fX;
2043 count *= 2;
2044
2045 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002046 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002047 if (i < count - 1) {
2048 str->append(", ");
2049 }
2050 }
reed@google.com277c3f82013-05-31 15:17:50 +00002051 if (conicWeight >= 0) {
2052 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002053 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002054 }
caryclark08fa28c2014-10-23 13:08:56 -07002055 str->append(");");
reede05fed02014-12-15 07:59:53 -08002056 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002057 str->append(" // ");
2058 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002059 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002060 if (i < count - 1) {
2061 str->append(", ");
2062 }
2063 }
2064 if (conicWeight >= 0) {
2065 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002066 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002067 }
2068 }
2069 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002070}
2071
caryclarke9562592014-09-15 09:26:09 -07002072void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002073 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 Iter iter(*this, forceClose);
2075 SkPoint pts[4];
2076 Verb verb;
2077
caryclark66a5d8b2014-06-24 08:30:15 -07002078 if (!wStream) {
2079 SkDebugf("path: forceClose=%s\n", forceClose ? "true" : "false");
2080 }
reed@google.com51bbe752013-01-17 22:07:50 +00002081 SkString builder;
2082
reed@google.com4a3b7142012-05-16 17:16:46 +00002083 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084 switch (verb) {
2085 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002086 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 break;
2088 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002089 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002090 break;
2091 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002092 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002094 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002095 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002096 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002098 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 break;
2100 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002101 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102 break;
2103 default:
2104 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2105 verb = kDone_Verb; // stop the loop
2106 break;
2107 }
caryclark1049f122015-04-20 08:31:59 -07002108 if (!wStream && builder.size()) {
2109 SkDebugf("%s", builder.c_str());
2110 builder.reset();
2111 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002112 }
caryclark66a5d8b2014-06-24 08:30:15 -07002113 if (wStream) {
2114 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002115 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116}
2117
reed@android.come522ca52009-11-23 20:10:41 +00002118void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002119 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002120}
2121
2122void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002123 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002124}
2125
2126#ifdef SK_DEBUG
2127void SkPath::validate() const {
reed@android.come522ca52009-11-23 20:10:41 +00002128 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002129
djsollen@google.com077348c2012-10-22 20:23:32 +00002130#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002131 if (!fBoundsIsDirty) {
2132 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002133
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002134 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002135 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002136
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002137 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002138 // if we're empty, fBounds may be empty but translated, so we can't
2139 // necessarily compare to bounds directly
2140 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2141 // be [2, 2, 2, 2]
2142 SkASSERT(bounds.isEmpty());
2143 SkASSERT(fBounds.isEmpty());
2144 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002145 if (bounds.isEmpty()) {
2146 SkASSERT(fBounds.isEmpty());
2147 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002148 if (!fBounds.isEmpty()) {
2149 SkASSERT(fBounds.contains(bounds));
2150 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002151 }
reed@android.come522ca52009-11-23 20:10:41 +00002152 }
2153 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002154#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002155}
djsollen@google.com077348c2012-10-22 20:23:32 +00002156#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002157
reed@google.com04863fa2011-05-15 04:08:24 +00002158///////////////////////////////////////////////////////////////////////////////
2159
reed@google.com0b7b9822011-05-16 12:29:27 +00002160static int sign(SkScalar x) { return x < 0; }
2161#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002162
robertphillipsc506e302014-09-16 09:43:31 -07002163enum DirChange {
2164 kLeft_DirChange,
2165 kRight_DirChange,
2166 kStraight_DirChange,
2167 kBackwards_DirChange,
2168
2169 kInvalid_DirChange
2170};
2171
2172
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002173static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002174 // The error epsilon was empirically derived; worse case round rects
2175 // with a mid point outset by 2x float epsilon in tests had an error
2176 // of 12.
2177 const int epsilon = 16;
2178 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2179 return false;
2180 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002181 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002182 int aBits = SkFloatAs2sCompliment(compA);
2183 int bBits = SkFloatAs2sCompliment(compB);
2184 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002185}
2186
caryclarkb4216502015-03-02 13:02:34 -08002187static bool approximately_zero_when_compared_to(double x, double y) {
2188 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002189}
2190
caryclarkb4216502015-03-02 13:02:34 -08002191
reed@google.com04863fa2011-05-15 04:08:24 +00002192// only valid for a single contour
2193struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002194 Convexicator()
2195 : fPtCount(0)
2196 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002197 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002198 , fIsFinite(true)
2199 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002200 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002201 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002202 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002203 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002204 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002205 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002206
2207 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002208 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002209 }
2210
2211 SkPath::Convexity getConvexity() const { return fConvexity; }
2212
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002213 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002214 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002215
reed@google.com04863fa2011-05-15 04:08:24 +00002216 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002217 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002218 return;
2219 }
2220
2221 if (0 == fPtCount) {
2222 fCurrPt = pt;
2223 ++fPtCount;
2224 } else {
2225 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002226 SkScalar lengthSqd = vec.lengthSqd();
2227 if (!SkScalarIsFinite(lengthSqd)) {
2228 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002229 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002230 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002231 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002232 fCurrPt = pt;
2233 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002234 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002235 } else {
2236 SkASSERT(fPtCount > 2);
2237 this->addVec(vec);
2238 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002239
reed@google.com85b6e392011-05-15 20:25:17 +00002240 int sx = sign(vec.fX);
2241 int sy = sign(vec.fY);
2242 fDx += (sx != fSx);
2243 fDy += (sy != fSy);
2244 fSx = sx;
2245 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002246
reed@google.com85b6e392011-05-15 20:25:17 +00002247 if (fDx > 3 || fDy > 3) {
2248 fConvexity = SkPath::kConcave_Convexity;
2249 }
reed@google.com04863fa2011-05-15 04:08:24 +00002250 }
2251 }
2252 }
2253
2254 void close() {
2255 if (fPtCount > 2) {
2256 this->addVec(fFirstVec);
2257 }
2258 }
2259
caryclarkb4216502015-03-02 13:02:34 -08002260 DirChange directionChange(const SkVector& curVec) {
2261 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2262
2263 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2264 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2265 largest = SkTMax(largest, -smallest);
2266
2267 if (!almost_equal(largest, largest + cross)) {
2268 int sign = SkScalarSignAsInt(cross);
2269 if (sign) {
2270 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2271 }
2272 }
2273
2274 if (cross) {
2275 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2276 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2277 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2278 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2279 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2280 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2281 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2282 if (sign) {
2283 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2284 }
2285 }
2286 }
2287
2288 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2289 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2290 fLastVec.dot(curVec) < 0.0f) {
2291 return kBackwards_DirChange;
2292 }
2293
2294 return kStraight_DirChange;
2295 }
2296
2297
caryclarkd3d1a982014-12-08 04:57:38 -08002298 bool isFinite() const {
2299 return fIsFinite;
2300 }
2301
caryclark5ccef572015-03-02 10:07:56 -08002302 void setCurve(bool isCurve) {
2303 fIsCurve = isCurve;
2304 }
2305
reed@google.com04863fa2011-05-15 04:08:24 +00002306private:
2307 void addVec(const SkVector& vec) {
2308 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002309 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002310 switch (dir) {
2311 case kLeft_DirChange: // fall through
2312 case kRight_DirChange:
2313 if (kInvalid_DirChange == fExpectedDir) {
2314 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002315 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2316 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002317 } else if (dir != fExpectedDir) {
2318 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002319 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002320 }
2321 fLastVec = vec;
2322 break;
2323 case kStraight_DirChange:
2324 break;
2325 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002326 if (fIsCurve) {
2327 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002328 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
caryclark5ccef572015-03-02 10:07:56 -08002329 }
robertphillipsc506e302014-09-16 09:43:31 -07002330 fLastVec = vec;
2331 break;
2332 case kInvalid_DirChange:
2333 SkFAIL("Use of invalid direction change flag");
2334 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002335 }
2336 }
2337
caryclarkb4216502015-03-02 13:02:34 -08002338 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002339 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002340 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002341 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2342 // value with the current vec is deemed to be of a significant value.
2343 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002344 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002345 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002346 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002347 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002348 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002349 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002350 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002351};
2352
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002353SkPath::Convexity SkPath::internalGetConvexity() const {
2354 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002355 SkPoint pts[4];
2356 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002357 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002358
2359 int contourCount = 0;
2360 int count;
2361 Convexicator state;
2362
caryclarkd3d1a982014-12-08 04:57:38 -08002363 if (!isFinite()) {
2364 return kUnknown_Convexity;
2365 }
caryclarke8c56662015-07-14 11:19:26 -07002366 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002367 switch (verb) {
2368 case kMove_Verb:
2369 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002370 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002371 return kConcave_Convexity;
2372 }
2373 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002374 // fall through
2375 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002376 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002377 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002378 break;
caryclark5ccef572015-03-02 10:07:56 -08002379 case kQuad_Verb:
2380 // fall through
2381 case kConic_Verb:
2382 // fall through
2383 case kCubic_Verb:
2384 count = 2 + (kCubic_Verb == verb);
2385 // As an additional enhancement, this could set curve true only
2386 // if the curve is nonlinear
2387 state.setCurve(true);
2388 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002389 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002390 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002391 state.close();
2392 count = 0;
2393 break;
2394 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002395 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002396 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002397 return kConcave_Convexity;
2398 }
2399
2400 for (int i = 1; i <= count; i++) {
2401 state.addPt(pts[i]);
2402 }
2403 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002404 if (!state.isFinite()) {
2405 return kUnknown_Convexity;
2406 }
reed@google.com04863fa2011-05-15 04:08:24 +00002407 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002408 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002409 return kConcave_Convexity;
2410 }
2411 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002412 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002413 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2414 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002415 }
2416 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002417}
reed@google.com69a99432012-01-10 18:00:10 +00002418
2419///////////////////////////////////////////////////////////////////////////////
2420
2421class ContourIter {
2422public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002423 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002424
2425 bool done() const { return fDone; }
2426 // if !done() then these may be called
2427 int count() const { return fCurrPtCount; }
2428 const SkPoint* pts() const { return fCurrPt; }
2429 void next();
2430
2431private:
2432 int fCurrPtCount;
2433 const SkPoint* fCurrPt;
2434 const uint8_t* fCurrVerb;
2435 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002436 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002437 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002438 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002439};
2440
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002441ContourIter::ContourIter(const SkPathRef& pathRef) {
2442 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002443 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002444 fCurrPt = pathRef.points();
2445 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002446 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002447 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002448 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002449 this->next();
2450}
2451
2452void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002453 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002454 fDone = true;
2455 }
2456 if (fDone) {
2457 return;
2458 }
2459
2460 // skip pts of prev contour
2461 fCurrPt += fCurrPtCount;
2462
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002463 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002464 int ptCount = 1; // moveTo
2465 const uint8_t* verbs = fCurrVerb;
2466
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002467 for (--verbs; verbs > fStopVerbs; --verbs) {
2468 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002469 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002470 goto CONTOUR_END;
2471 case SkPath::kLine_Verb:
2472 ptCount += 1;
2473 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002474 case SkPath::kConic_Verb:
2475 fCurrConicWeight += 1;
2476 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002477 case SkPath::kQuad_Verb:
2478 ptCount += 2;
2479 break;
2480 case SkPath::kCubic_Verb:
2481 ptCount += 3;
2482 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002483 case SkPath::kClose_Verb:
2484 break;
2485 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002486 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002487 break;
2488 }
2489 }
2490CONTOUR_END:
2491 fCurrPtCount = ptCount;
2492 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002493 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002494}
2495
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002496// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002497static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002498 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2499 // We may get 0 when the above subtracts underflow. We expect this to be
2500 // very rare and lazily promote to double.
2501 if (0 == cross) {
2502 double p0x = SkScalarToDouble(p0.fX);
2503 double p0y = SkScalarToDouble(p0.fY);
2504
2505 double p1x = SkScalarToDouble(p1.fX);
2506 double p1y = SkScalarToDouble(p1.fY);
2507
2508 double p2x = SkScalarToDouble(p2.fX);
2509 double p2y = SkScalarToDouble(p2.fY);
2510
2511 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2512 (p1y - p0y) * (p2x - p0x));
2513
2514 }
2515 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002516}
2517
reed@google.comc1ea60a2012-01-31 15:15:36 +00002518// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002519static int find_max_y(const SkPoint pts[], int count) {
2520 SkASSERT(count > 0);
2521 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002522 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002523 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002524 SkScalar y = pts[i].fY;
2525 if (y > max) {
2526 max = y;
2527 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002528 }
2529 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002530 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002531}
2532
reed@google.comcabaf1d2012-01-11 21:03:05 +00002533static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2534 int i = index;
2535 for (;;) {
2536 i = (i + inc) % n;
2537 if (i == index) { // we wrapped around, so abort
2538 break;
2539 }
2540 if (pts[index] != pts[i]) { // found a different point, success!
2541 break;
2542 }
2543 }
2544 return i;
2545}
2546
reed@google.comc1ea60a2012-01-31 15:15:36 +00002547/**
2548 * Starting at index, and moving forward (incrementing), find the xmin and
2549 * xmax of the contiguous points that have the same Y.
2550 */
2551static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2552 int* maxIndexPtr) {
2553 const SkScalar y = pts[index].fY;
2554 SkScalar min = pts[index].fX;
2555 SkScalar max = min;
2556 int minIndex = index;
2557 int maxIndex = index;
2558 for (int i = index + 1; i < n; ++i) {
2559 if (pts[i].fY != y) {
2560 break;
2561 }
2562 SkScalar x = pts[i].fX;
2563 if (x < min) {
2564 min = x;
2565 minIndex = i;
2566 } else if (x > max) {
2567 max = x;
2568 maxIndex = i;
2569 }
2570 }
2571 *maxIndexPtr = maxIndex;
2572 return minIndex;
2573}
2574
reed026beb52015-06-10 14:23:15 -07002575static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2576 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002577}
2578
reed@google.comac8543f2012-01-30 20:51:25 +00002579/*
2580 * We loop through all contours, and keep the computed cross-product of the
2581 * contour that contained the global y-max. If we just look at the first
2582 * contour, we may find one that is wound the opposite way (correctly) since
2583 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2584 * that is outer most (or at least has the global y-max) before we can consider
2585 * its cross product.
2586 */
reed026beb52015-06-10 14:23:15 -07002587bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002588 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2589 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002590 return true;
2591 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002592
2593 // don't want to pay the cost for computing this if it
2594 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002595 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2596 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002597 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002598 return false;
2599 }
reed@google.com69a99432012-01-10 18:00:10 +00002600
reed026beb52015-06-10 14:23:15 -07002601 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002602
reed@google.comac8543f2012-01-30 20:51:25 +00002603 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002604 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002605 SkScalar ymaxCross = 0;
2606
reed@google.com69a99432012-01-10 18:00:10 +00002607 for (; !iter.done(); iter.next()) {
2608 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002609 if (n < 3) {
2610 continue;
2611 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002612
reed@google.comcabaf1d2012-01-11 21:03:05 +00002613 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002614 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002615 int index = find_max_y(pts, n);
2616 if (pts[index].fY < ymax) {
2617 continue;
2618 }
2619
2620 // If there is more than 1 distinct point at the y-max, we take the
2621 // x-min and x-max of them and just subtract to compute the dir.
2622 if (pts[(index + 1) % n].fY == pts[index].fY) {
2623 int maxIndex;
2624 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2625 if (minIndex == maxIndex) {
2626 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002627 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002628 SkASSERT(pts[minIndex].fY == pts[index].fY);
2629 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2630 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2631 // we just subtract the indices, and let that auto-convert to
2632 // SkScalar, since we just want - or + to signal the direction.
2633 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002634 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002635 TRY_CROSSPROD:
2636 // Find a next and prev index to use for the cross-product test,
2637 // but we try to find pts that form non-zero vectors from pts[index]
2638 //
2639 // Its possible that we can't find two non-degenerate vectors, so
2640 // we have to guard our search (e.g. all the pts could be in the
2641 // same place).
2642
2643 // we pass n - 1 instead of -1 so we don't foul up % operator by
2644 // passing it a negative LH argument.
2645 int prev = find_diff_pt(pts, index, n, n - 1);
2646 if (prev == index) {
2647 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002648 continue;
2649 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002650 int next = find_diff_pt(pts, index, n, 1);
2651 SkASSERT(next != index);
2652 cross = cross_prod(pts[prev], pts[index], pts[next]);
2653 // if we get a zero and the points are horizontal, then we look at the spread in
2654 // x-direction. We really should continue to walk away from the degeneracy until
2655 // there is a divergence.
2656 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2657 // construct the subtract so we get the correct Direction below
2658 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002659 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002660 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002661
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002662 if (cross) {
2663 // record our best guess so far
2664 ymax = pts[index].fY;
2665 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002666 }
2667 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002668 if (ymaxCross) {
2669 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002670 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002671 return true;
2672 } else {
2673 return false;
2674 }
reed@google.comac8543f2012-01-30 20:51:25 +00002675}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002676
2677///////////////////////////////////////////////////////////////////////////////
2678
caryclark9aacd902015-12-14 08:38:09 -08002679static bool between(SkScalar a, SkScalar b, SkScalar c) {
2680 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2681 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2682 return (a - b) * (c - b) <= 0;
2683}
2684
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002685static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2686 SkScalar D, SkScalar t) {
2687 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2688}
2689
2690static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2691 SkScalar t) {
2692 SkScalar A = c3 + 3*(c1 - c2) - c0;
2693 SkScalar B = 3*(c2 - c1 - c1 + c0);
2694 SkScalar C = 3*(c1 - c0);
2695 SkScalar D = c0;
2696 return eval_cubic_coeff(A, B, C, D, t);
2697}
2698
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002699template <size_t N> static void find_minmax(const SkPoint pts[],
2700 SkScalar* minPtr, SkScalar* maxPtr) {
2701 SkScalar min, max;
2702 min = max = pts[0].fX;
2703 for (size_t i = 1; i < N; ++i) {
2704 min = SkMinScalar(min, pts[i].fX);
2705 max = SkMaxScalar(max, pts[i].fX);
2706 }
2707 *minPtr = min;
2708 *maxPtr = max;
2709}
2710
caryclark9cb5d752015-12-18 04:35:24 -08002711static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2712 if (start.fY == end.fY) {
2713 return between(start.fX, x, end.fX) && x != end.fX;
2714 } else {
2715 return x == start.fX && y == start.fY;
2716 }
2717}
2718
caryclark9aacd902015-12-14 08:38:09 -08002719static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002720 SkScalar y0 = pts[0].fY;
2721 SkScalar y3 = pts[3].fY;
2722
2723 int dir = 1;
2724 if (y0 > y3) {
2725 SkTSwap(y0, y3);
2726 dir = -1;
2727 }
2728 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002729 return 0;
2730 }
caryclark9cb5d752015-12-18 04:35:24 -08002731 if (checkOnCurve(x, y, pts[0], pts[3])) {
2732 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002733 return 0;
2734 }
caryclark9cb5d752015-12-18 04:35:24 -08002735 if (y == y3) {
2736 return 0;
2737 }
2738
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002739 // quickreject or quickaccept
2740 SkScalar min, max;
2741 find_minmax<4>(pts, &min, &max);
2742 if (x < min) {
2743 return 0;
2744 }
2745 if (x > max) {
2746 return dir;
2747 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002750 SkScalar t;
caryclark9aacd902015-12-14 08:38:09 -08002751 SkAssertResult(SkCubicClipper::ChopMonoAtY(pts, y, &t));
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002752 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002753 if (SkScalarNearlyEqual(xt, x)) {
2754 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2755 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002756 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002757 }
2758 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002759 return xt < x ? dir : 0;
2760}
2761
caryclark9aacd902015-12-14 08:38:09 -08002762static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 SkPoint dst[10];
2764 int n = SkChopCubicAtYExtrema(pts, dst);
2765 int w = 0;
2766 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002767 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002768 }
2769 return w;
2770}
2771
caryclark9aacd902015-12-14 08:38:09 -08002772static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2773 SkASSERT(src);
2774 SkASSERT(t >= 0 && t <= 1);
2775 SkScalar src2w = src[2] * w;
2776 SkScalar C = src[0];
2777 SkScalar A = src[4] - 2 * src2w + C;
2778 SkScalar B = 2 * (src2w - C);
2779 return (A * t + B) * t + C;
2780}
2781
2782
2783static double conic_eval_denominator(SkScalar w, SkScalar t) {
2784 SkScalar B = 2 * (w - 1);
2785 SkScalar C = 1;
2786 SkScalar A = -B;
2787 return (A * t + B) * t + C;
2788}
2789
2790static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2791 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792 SkScalar y0 = pts[0].fY;
2793 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002794
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795 int dir = 1;
2796 if (y0 > y2) {
2797 SkTSwap(y0, y2);
2798 dir = -1;
2799 }
caryclark9aacd902015-12-14 08:38:09 -08002800 if (y < y0 || y > y2) {
2801 return 0;
2802 }
caryclark9cb5d752015-12-18 04:35:24 -08002803 if (checkOnCurve(x, y, pts[0], pts[2])) {
2804 *onCurveCount += 1;
2805 return 0;
2806 }
caryclark9aacd902015-12-14 08:38:09 -08002807 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 return 0;
2809 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002810
caryclark9aacd902015-12-14 08:38:09 -08002811 SkScalar roots[2];
2812 SkScalar A = pts[2].fY;
2813 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2814 SkScalar C = pts[0].fY;
2815 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2816 B -= C; // B = b*w - w * yCept + yCept - a
2817 C -= y;
2818 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2819 SkASSERT(n <= 1);
2820 SkScalar xt;
2821 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002822 // zero roots are returned only when y0 == y
2823 // Need [0] if dir == 1
2824 // and [2] if dir == -1
2825 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002826 } else {
2827 SkScalar t = roots[0];
2828 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2829 }
2830 if (SkScalarNearlyEqual(xt, x)) {
2831 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2832 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002833 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002834 }
2835 }
2836 return xt < x ? dir : 0;
2837}
2838
2839static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2840 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2841 if (y0 == y1) {
2842 return true;
2843 }
2844 if (y0 < y1) {
2845 return y1 <= y2;
2846 } else {
2847 return y1 >= y2;
2848 }
2849}
2850
2851static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2852 int* onCurveCount) {
2853 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002854 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002855 // If the data points are very large, the conic may not be monotonic but may also
2856 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002857 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2858 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2859 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002860 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2861 }
2862 return w;
2863}
2864
2865static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2866 SkScalar y0 = pts[0].fY;
2867 SkScalar y2 = pts[2].fY;
2868
2869 int dir = 1;
2870 if (y0 > y2) {
2871 SkTSwap(y0, y2);
2872 dir = -1;
2873 }
2874 if (y < y0 || y > y2) {
2875 return 0;
2876 }
caryclark9cb5d752015-12-18 04:35:24 -08002877 if (checkOnCurve(x, y, pts[0], pts[2])) {
2878 *onCurveCount += 1;
2879 return 0;
2880 }
caryclark9aacd902015-12-14 08:38:09 -08002881 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002882 return 0;
2883 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884 // bounds check on X (not required. is it faster?)
2885#if 0
2886 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2887 return 0;
2888 }
2889#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002890
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002891 SkScalar roots[2];
2892 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2893 2 * (pts[1].fY - pts[0].fY),
2894 pts[0].fY - y,
2895 roots);
2896 SkASSERT(n <= 1);
2897 SkScalar xt;
2898 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002899 // zero roots are returned only when y0 == y
2900 // Need [0] if dir == 1
2901 // and [2] if dir == -1
2902 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002903 } else {
2904 SkScalar t = roots[0];
2905 SkScalar C = pts[0].fX;
2906 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2907 SkScalar B = 2 * (pts[1].fX - C);
2908 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2909 }
caryclark9aacd902015-12-14 08:38:09 -08002910 if (SkScalarNearlyEqual(xt, x)) {
2911 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2912 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002913 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002914 }
2915 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002916 return xt < x ? dir : 0;
2917}
2918
caryclark9aacd902015-12-14 08:38:09 -08002919static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 SkPoint dst[5];
2921 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002922
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002923 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2924 n = SkChopQuadAtYExtrema(pts, dst);
2925 pts = dst;
2926 }
caryclark9aacd902015-12-14 08:38:09 -08002927 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002928 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002929 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002930 }
2931 return w;
2932}
2933
caryclark9aacd902015-12-14 08:38:09 -08002934static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002935 SkScalar x0 = pts[0].fX;
2936 SkScalar y0 = pts[0].fY;
2937 SkScalar x1 = pts[1].fX;
2938 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002939
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002940 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002941
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002942 int dir = 1;
2943 if (y0 > y1) {
2944 SkTSwap(y0, y1);
2945 dir = -1;
2946 }
caryclark9aacd902015-12-14 08:38:09 -08002947 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948 return 0;
2949 }
caryclark9cb5d752015-12-18 04:35:24 -08002950 if (checkOnCurve(x, y, pts[0], pts[1])) {
2951 *onCurveCount += 1;
2952 return 0;
2953 }
2954 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08002955 return 0;
2956 }
2957 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) - SkScalarMul(dy, x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002958
caryclark9aacd902015-12-14 08:38:09 -08002959 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08002960 // zero cross means the point is on the line, and since the case where
2961 // y of the query point is at the end point is handled above, we can be
2962 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08002963 if (x != x1 || y != pts[1].fY) {
2964 *onCurveCount += 1;
2965 }
caryclark9aacd902015-12-14 08:38:09 -08002966 dir = 0;
2967 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002968 dir = 0;
2969 }
2970 return dir;
2971}
2972
caryclark9aacd902015-12-14 08:38:09 -08002973static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
2974 SkTDArray<SkVector>* tangents) {
2975 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
2976 && !between(pts[2].fY, y, pts[3].fY)) {
2977 return;
2978 }
2979 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
2980 && !between(pts[2].fX, x, pts[3].fX)) {
2981 return;
2982 }
2983 SkPoint dst[10];
2984 int n = SkChopCubicAtYExtrema(pts, dst);
2985 for (int i = 0; i <= n; ++i) {
2986 SkPoint* c = &dst[i * 3];
2987 SkScalar t;
2988 SkAssertResult(SkCubicClipper::ChopMonoAtY(c, y, &t));
2989 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
2990 if (!SkScalarNearlyEqual(x, xt)) {
2991 continue;
2992 }
2993 SkVector tangent;
2994 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
2995 tangents->push(tangent);
2996 }
2997}
2998
2999static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3000 SkTDArray<SkVector>* tangents) {
3001 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3002 return;
3003 }
3004 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3005 return;
3006 }
3007 SkScalar roots[2];
3008 SkScalar A = pts[2].fY;
3009 SkScalar B = pts[1].fY * w - y * w + y;
3010 SkScalar C = pts[0].fY;
3011 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3012 B -= C; // B = b*w - w * yCept + yCept - a
3013 C -= y;
3014 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3015 for (int index = 0; index < n; ++index) {
3016 SkScalar t = roots[index];
3017 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3018 if (!SkScalarNearlyEqual(x, xt)) {
3019 continue;
3020 }
3021 SkConic conic(pts, w);
3022 tangents->push(conic.evalTangentAt(t));
3023 }
3024}
3025
3026static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3027 SkTDArray<SkVector>* tangents) {
3028 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3029 return;
3030 }
3031 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3032 return;
3033 }
3034 SkScalar roots[2];
3035 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3036 2 * (pts[1].fY - pts[0].fY),
3037 pts[0].fY - y,
3038 roots);
3039 for (int index = 0; index < n; ++index) {
3040 SkScalar t = roots[index];
3041 SkScalar C = pts[0].fX;
3042 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3043 SkScalar B = 2 * (pts[1].fX - C);
3044 SkScalar xt = (A * t + B) * t + C;
3045 if (!SkScalarNearlyEqual(x, xt)) {
3046 continue;
3047 }
3048 tangents->push(SkEvalQuadTangentAt(pts, t));
3049 }
3050}
3051
3052static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3053 SkTDArray<SkVector>* tangents) {
3054 SkScalar y0 = pts[0].fY;
3055 SkScalar y1 = pts[1].fY;
3056 if (!between(y0, y, y1)) {
3057 return;
3058 }
3059 SkScalar x0 = pts[0].fX;
3060 SkScalar x1 = pts[1].fX;
3061 if (!between(x0, x, x1)) {
3062 return;
3063 }
3064 SkScalar dx = x1 - x0;
3065 SkScalar dy = y1 - y0;
3066 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3067 return;
3068 }
3069 SkVector v;
3070 v.set(dx, dy);
3071 tangents->push(v);
3072}
3073
reed@google.com4db592c2013-10-30 17:39:43 +00003074static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3075 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3076}
3077
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003078bool SkPath::contains(SkScalar x, SkScalar y) const {
3079 bool isInverse = this->isInverseFillType();
3080 if (this->isEmpty()) {
3081 return isInverse;
3082 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003083
reed@google.com4db592c2013-10-30 17:39:43 +00003084 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003085 return isInverse;
3086 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003087
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003088 SkPath::Iter iter(*this, true);
3089 bool done = false;
3090 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003091 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003092 do {
3093 SkPoint pts[4];
3094 switch (iter.next(pts, false)) {
3095 case SkPath::kMove_Verb:
3096 case SkPath::kClose_Verb:
3097 break;
3098 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003099 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003100 break;
3101 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003102 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003103 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003104 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003105 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003106 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003107 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003108 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003109 break;
3110 case SkPath::kDone_Verb:
3111 done = true;
3112 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003113 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003114 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003115 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3116 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3117 if (evenOddFill) {
3118 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003119 }
caryclark9aacd902015-12-14 08:38:09 -08003120 if (w) {
3121 return !isInverse;
3122 }
3123 if (onCurveCount <= 1) {
3124 return SkToBool(onCurveCount) ^ isInverse;
3125 }
3126 if ((onCurveCount & 1) || evenOddFill) {
3127 return SkToBool(onCurveCount & 1) ^ isInverse;
3128 }
3129 // If the point touches an even number of curves, and the fill is winding, check for
3130 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3131 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003132 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003133 SkTDArray<SkVector> tangents;
3134 do {
3135 SkPoint pts[4];
3136 int oldCount = tangents.count();
3137 switch (iter.next(pts, false)) {
3138 case SkPath::kMove_Verb:
3139 case SkPath::kClose_Verb:
3140 break;
3141 case SkPath::kLine_Verb:
3142 tangent_line(pts, x, y, &tangents);
3143 break;
3144 case SkPath::kQuad_Verb:
3145 tangent_quad(pts, x, y, &tangents);
3146 break;
3147 case SkPath::kConic_Verb:
3148 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3149 break;
3150 case SkPath::kCubic_Verb:
3151 tangent_cubic(pts, x, y, &tangents);
3152 break;
3153 case SkPath::kDone_Verb:
3154 done = true;
3155 break;
3156 }
3157 if (tangents.count() > oldCount) {
3158 int last = tangents.count() - 1;
3159 const SkVector& tangent = tangents[last];
3160 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3161 tangents.remove(last);
3162 } else {
3163 for (int index = 0; index < last; ++index) {
3164 const SkVector& test = tangents[index];
3165 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003166 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3167 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003168 tangents.remove(last);
3169 tangents.removeShuffle(index);
3170 break;
3171 }
3172 }
3173 }
3174 }
3175 } while (!done);
3176 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003177}
fmalitaaa0df4e2015-12-01 09:13:23 -08003178
3179int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3180 SkScalar w, SkPoint pts[], int pow2) {
3181 const SkConic conic(p0, p1, p2, w);
3182 return conic.chopIntoQuadsPOW2(pts, pow2);
3183}