blob: 1cc92a41b4cf9120b1f87d2aa881785fd895ba3b [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
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
reed026beb52015-06-10 14:23:15 -070014#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000016#include "SkRRect.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
Mike Reeda99b6ce2017-02-04 11:04:26 -050018static float poly_eval(float A, float B, float C, float t) {
19 return (A * t + B) * t + C;
20}
21
22static float poly_eval(float A, float B, float C, float D, float t) {
23 return ((A * t + B) * t + C) * t + D;
24}
25
reed@android.com8a1c16f2008-12-17 15:59:43 +000026////////////////////////////////////////////////////////////////////////////
27
reed@google.com3563c9e2011-11-14 19:34:57 +000028/**
29 * Path.bounds is defined to be the bounds of all the control points.
30 * If we called bounds.join(r) we would skip r if r was empty, which breaks
31 * our promise. Hence we have a custom joiner that doesn't look at emptiness
32 */
33static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
34 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
35 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
36 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
37 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
38}
39
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000040static bool is_degenerate(const SkPath& path) {
41 SkPath::Iter iter(path, false);
42 SkPoint pts[4];
43 return SkPath::kDone_Verb == iter.next(pts);
44}
45
bsalomon@google.com30c174b2012-11-13 14:36:42 +000046class SkAutoDisableDirectionCheck {
47public:
48 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070049 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000050 }
51
52 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070053 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000054 }
55
56private:
reed026beb52015-06-10 14:23:15 -070057 SkPath* fPath;
58 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000059};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000060#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000061
reed@android.com8a1c16f2008-12-17 15:59:43 +000062/* This guy's constructor/destructor bracket a path editing operation. It is
63 used when we know the bounds of the amount we are going to add to the path
64 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000065
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000067 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000068 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000069
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000070 It also notes if the path was originally degenerate, and if so, sets
71 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000072 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000073 */
74class SkAutoPathBoundsUpdate {
75public:
76 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
77 this->init(path);
78 }
79
80 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
81 SkScalar right, SkScalar bottom) {
82 fRect.set(left, top, right, bottom);
83 this->init(path);
84 }
reed@google.comabf15c12011-01-18 20:35:51 +000085
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000087 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
88 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +000089 if (fEmpty || fHasValidBounds) {
90 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000091 }
92 }
reed@google.comabf15c12011-01-18 20:35:51 +000093
reed@android.com8a1c16f2008-12-17 15:59:43 +000094private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000095 SkPath* fPath;
96 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +000097 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000098 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +000099 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000100
reed@android.com6b82d1a2009-06-03 02:35:01 +0000101 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000102 // Cannot use fRect for our bounds unless we know it is sorted
103 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000105 // Mark the path's bounds as dirty if (1) they are, or (2) the path
106 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000107 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000108 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000109 if (fHasValidBounds && !fEmpty) {
110 joinNoEmptyChecks(&fRect, fPath->getBounds());
111 }
112 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113 }
114};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000115#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117////////////////////////////////////////////////////////////////////////////
118
119/*
120 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000121 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000122 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
123
124 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000125 1. If we encounter degenerate segments, remove them
126 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
127 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
128 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129*/
130
131////////////////////////////////////////////////////////////////////////////
132
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000133// flag to require a moveTo if we begin with something else, like lineTo etc.
134#define INITIAL_LASTMOVETOINDEX_VALUE ~0
135
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000136SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800137 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000138 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700139 fIsVolatile = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000140}
141
142void SkPath::resetFields() {
143 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000144 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000145 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000146 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700147 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000148
149 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700150 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000151}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152
bungeman@google.coma5809a32013-06-21 15:13:34 +0000153SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000154 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000155 this->copyFields(that);
156 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000157}
158
159SkPath::~SkPath() {
160 SkDEBUGCODE(this->validate();)
161}
162
bungeman@google.coma5809a32013-06-21 15:13:34 +0000163SkPath& SkPath::operator=(const SkPath& that) {
164 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166 if (this != &that) {
167 fPathRef.reset(SkRef(that.fPathRef.get()));
168 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169 }
170 SkDEBUGCODE(this->validate();)
171 return *this;
172}
173
bungeman@google.coma5809a32013-06-21 15:13:34 +0000174void SkPath::copyFields(const SkPath& that) {
175 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000176 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177 fFillType = that.fFillType;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178 fConvexity = that.fConvexity;
herb9f4dbca2015-09-28 11:05:47 -0700179 // Simulate fFirstDirection = that.fFirstDirection;
180 fFirstDirection.store(that.fFirstDirection.load());
jvanverthb3eb6872014-10-24 07:12:51 -0700181 fIsVolatile = that.fIsVolatile;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182}
183
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000184bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185 // note: don't need to look at isConvex or bounds, since just comparing the
186 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000187 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000188 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000189}
190
bungeman@google.coma5809a32013-06-21 15:13:34 +0000191void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000192 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700193 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000194 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195 SkTSwap<uint8_t>(fFillType, that.fFillType);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
herb9f4dbca2015-09-28 11:05:47 -0700197 // Simulate SkTSwap<uint8_t>(fFirstDirection, that.fFirstDirection);
198 uint8_t temp = fFirstDirection;
199 fFirstDirection.store(that.fFirstDirection.load());
200 that.fFirstDirection.store(temp);
jvanverthb3eb6872014-10-24 07:12:51 -0700201 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 }
203}
204
caryclark8e7b19d2016-02-18 04:11:48 -0800205bool SkPath::isInterpolatable(const SkPath& compare) const {
206 int count = fPathRef->countVerbs();
207 if (count != compare.fPathRef->countVerbs()) {
208 return false;
209 }
210 if (!count) {
211 return true;
212 }
213 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
214 count)) {
215 return false;
216 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800217 return !fPathRef->countWeights() ||
218 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800219 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
220}
221
222bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
223 int verbCount = fPathRef->countVerbs();
224 if (verbCount != ending.fPathRef->countVerbs()) {
225 return false;
226 }
caryclarka1105382016-02-18 06:13:25 -0800227 if (!verbCount) {
228 return true;
229 }
caryclark8e7b19d2016-02-18 04:11:48 -0800230 out->reset();
231 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700232 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800233 return true;
234}
235
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000236static inline bool check_edge_against_rect(const SkPoint& p0,
237 const SkPoint& p1,
238 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700239 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000240 const SkPoint* edgeBegin;
241 SkVector v;
reed026beb52015-06-10 14:23:15 -0700242 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000243 v = p1 - p0;
244 edgeBegin = &p0;
245 } else {
246 v = p0 - p1;
247 edgeBegin = &p1;
248 }
249 if (v.fX || v.fY) {
250 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500251 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
252 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
253 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
254 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000255 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
256 return false;
257 }
258 }
259 return true;
260}
261
262bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
263 // This only handles non-degenerate convex paths currently.
264 if (kConvex_Convexity != this->getConvexity()) {
265 return false;
266 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000267
reed026beb52015-06-10 14:23:15 -0700268 SkPathPriv::FirstDirection direction;
269 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 return false;
271 }
272
273 SkPoint firstPt;
274 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500275 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000276 SkPath::Verb verb;
277 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400278 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000279 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281
Lee Salzmana19f0242017-01-12 13:06:21 -0500282 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000283 int nextPt = -1;
284 switch (verb) {
285 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000286 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 SkDEBUGCODE(++moveCnt);
288 firstPt = prevPt = pts[0];
289 break;
290 case kLine_Verb:
291 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000292 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400293 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 break;
295 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000296 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000297 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400298 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299 nextPt = 2;
300 break;
301 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 nextPt = 3;
305 break;
306 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000308 break;
309 default:
310 SkDEBUGFAIL("unknown verb");
311 }
312 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800313 if (SkPath::kConic_Verb == verb) {
314 SkConic orig;
315 orig.set(pts, iter.conicWeight());
316 SkPoint quadPts[5];
317 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800318 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800319
320 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
321 return false;
322 }
323 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
324 return false;
325 }
326 } else {
327 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
328 return false;
329 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000330 }
331 prevPt = pts[nextPt];
332 }
333 }
334
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400335 if (segmentCount) {
336 return check_edge_against_rect(prevPt, firstPt, rect, direction);
337 }
338 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000339}
340
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000341uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000342 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800343#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000344 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
345 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
346#endif
347 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000348}
djsollen@google.come63793a2012-03-21 15:39:03 +0000349
reed@android.com8a1c16f2008-12-17 15:59:43 +0000350void SkPath::reset() {
351 SkDEBUGCODE(this->validate();)
352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000353 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000354 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
357void SkPath::rewind() {
358 SkDEBUGCODE(this->validate();)
359
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000360 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000361 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362}
363
fsb1475b02016-01-20 09:51:07 -0800364bool SkPath::isLastContourClosed() const {
365 int verbCount = fPathRef->countVerbs();
366 if (0 == verbCount) {
367 return false;
368 }
369 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
370}
371
reed@google.com7e6c4d12012-05-10 14:05:43 +0000372bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000373 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000374
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000375 if (2 == verbCount) {
376 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
377 if (kLine_Verb == fPathRef->atVerb(1)) {
378 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000379 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000380 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000381 line[0] = pts[0];
382 line[1] = pts[1];
383 }
384 return true;
385 }
386 }
387 return false;
388}
389
caryclark@google.comf1316942011-07-26 19:54:45 +0000390/*
391 Determines if path is a rect by keeping track of changes in direction
392 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000393
caryclark@google.comf1316942011-07-26 19:54:45 +0000394 The direction is computed such that:
395 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000396 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000397 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000398 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400A rectangle cycles up/right/down/left or up/left/down/right.
401
402The test fails if:
403 The path is closed, and followed by a line.
404 A second move creates a new endpoint.
405 A diagonal line is parsed.
406 There's more than four changes of direction.
407 There's a discontinuity on the line (e.g., a move in the middle)
408 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 The path contains a quadratic or cubic.
410 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000411 *The rectangle doesn't complete a cycle.
412 *The final point isn't equal to the first point.
413
414 *These last two conditions we relax if we have a 3-edge path that would
415 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000416
caryclark@google.comf1316942011-07-26 19:54:45 +0000417It's OK if the path has:
418 Several colinear line segments composing a rectangle side.
419 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000420
caryclark@google.comf1316942011-07-26 19:54:45 +0000421The direction takes advantage of the corners found since opposite sides
422must travel in opposite directions.
423
424FIXME: Allow colinear quads and cubics to be treated like lines.
425FIXME: If the API passes fill-only, return true if the filled stroke
426 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000427
428 first,last,next direction state-machine:
429 0x1 is set if the segment is horizontal
430 0x2 is set if the segment is moving to the right or down
431 thus:
432 two directions are opposites iff (dirA ^ dirB) == 0x2
433 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000434
caryclark@google.comf1316942011-07-26 19:54:45 +0000435 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000436static int rect_make_dir(SkScalar dx, SkScalar dy) {
437 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
438}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000439bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
440 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000441 int corners = 0;
442 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000443 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700444 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000445 first.set(0, 0);
446 last.set(0, 0);
447 int firstDirection = 0;
448 int lastDirection = 0;
449 int nextDirection = 0;
450 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000451 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700452 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000453 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000454 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700455 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
456 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000457 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000458 savePts = pts;
459 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700461 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 case kLine_Verb: {
463 SkScalar left = last.fX;
464 SkScalar top = last.fY;
465 SkScalar right = pts->fX;
466 SkScalar bottom = pts->fY;
467 ++pts;
468 if (left != right && top != bottom) {
469 return false; // diagonal
470 }
471 if (left == right && top == bottom) {
472 break; // single point on side OK
473 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000474 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 if (0 == corners) {
476 firstDirection = nextDirection;
477 first = last;
478 last = pts[-1];
479 corners = 1;
480 closedOrMoved = false;
481 break;
482 }
483 if (closedOrMoved) {
484 return false; // closed followed by a line
485 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000486 if (autoClose && nextDirection == firstDirection) {
487 break; // colinear with first
488 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 closedOrMoved = autoClose;
490 if (lastDirection != nextDirection) {
491 if (++corners > 4) {
492 return false; // too many direction changes
493 }
494 }
495 last = pts[-1];
496 if (lastDirection == nextDirection) {
497 break; // colinear segment
498 }
499 // Possible values for corners are 2, 3, and 4.
500 // When corners == 3, nextDirection opposes firstDirection.
501 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000502 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
504 if ((directionCycle ^ turn) != nextDirection) {
505 return false; // direction didn't follow cycle
506 }
507 break;
508 }
509 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000510 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 case kCubic_Verb:
512 return false; // quadratic, cubic not allowed
513 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700514 if (allowPartial && !autoClose && firstDirection) {
515 insertClose = true;
516 *currVerb -= 1; // try move again afterwards
517 goto addMissingClose;
518 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 last = *pts++;
520 closedOrMoved = true;
521 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000522 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000523 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000524 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000525 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000527 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700528addMissingClose:
529 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000530 }
531 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000532 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000533 if (!result) {
534 // check if we are just an incomplete rectangle, in which case we can
535 // return true, but not claim to be closed.
536 // e.g.
537 // 3 sided rectangle
538 // 4 sided but the last edge is not long enough to reach the start
539 //
540 SkScalar closeX = first.x() - last.x();
541 SkScalar closeY = first.y() - last.y();
542 if (closeX && closeY) {
543 return false; // we're diagonal, abort (can we ever reach this?)
544 }
545 int closeDirection = rect_make_dir(closeX, closeY);
546 // make sure the close-segment doesn't double-back on itself
547 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
548 result = true;
549 autoClose = false; // we are not closed
550 }
551 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000552 if (savePts) {
553 *ptsPtr = savePts;
554 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000555 if (result && isClosed) {
556 *isClosed = autoClose;
557 }
558 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000559 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000560 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000561 return result;
562}
563
robertphillips4f662e62014-12-29 14:06:51 -0800564bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000565 SkDEBUGCODE(this->validate();)
566 int currVerb = 0;
567 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800568 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800569 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800570 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000571 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800572 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800573 int32_t num = SkToS32(pts - first);
574 if (num) {
575 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800576 } else {
577 // 'pts' isn't updated for open rects
578 *rect = this->getBounds();
579 }
580 }
581 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000582}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000583
caryclark95bc5f32015-04-08 08:34:15 -0700584bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000585 SkDEBUGCODE(this->validate();)
586 int currVerb = 0;
587 const SkPoint* pts = fPathRef->points();
588 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000589 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700590 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000591 return false;
592 }
593 const SkPoint* last = pts;
594 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700595 bool isClosed;
596 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000597 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700598 if (!isClosed) {
599 pts = fPathRef->points() + fPathRef->countPoints();
600 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000601 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602 if (testRects[0].contains(testRects[1])) {
603 if (rects) {
604 rects[0] = testRects[0];
605 rects[1] = testRects[1];
606 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000607 if (dirs) {
608 dirs[0] = testDirs[0];
609 dirs[1] = testDirs[1];
610 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 return true;
612 }
613 if (testRects[1].contains(testRects[0])) {
614 if (rects) {
615 rects[0] = testRects[1];
616 rects[1] = testRects[0];
617 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000618 if (dirs) {
619 dirs[0] = testDirs[1];
620 dirs[1] = testDirs[0];
621 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000622 return true;
623 }
624 }
625 return false;
626}
627
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000628int SkPath::countPoints() const {
629 return fPathRef->countPoints();
630}
631
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000632int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 SkDEBUGCODE(this->validate();)
634
635 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000636 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000637 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800638 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000639 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640}
641
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000642SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
644 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000645 }
646 return SkPoint::Make(0, 0);
647}
648
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649int SkPath::countVerbs() const {
650 return fPathRef->countVerbs();
651}
652
653static inline void copy_verbs_reverse(uint8_t* inorderDst,
654 const uint8_t* reversedSrc,
655 int count) {
656 for (int i = 0; i < count; ++i) {
657 inorderDst[i] = reversedSrc[~i];
658 }
659}
660
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000661int SkPath::getVerbs(uint8_t dst[], int max) const {
662 SkDEBUGCODE(this->validate();)
663
664 SkASSERT(max >= 0);
665 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 int count = SkMin32(max, fPathRef->countVerbs());
667 copy_verbs_reverse(dst, fPathRef->verbs(), count);
668 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000669}
670
reed@google.com294dd7b2011-10-11 11:58:32 +0000671bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000672 SkDEBUGCODE(this->validate();)
673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000674 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000675 if (count > 0) {
676 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000679 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000680 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000681 if (lastPt) {
682 lastPt->set(0, 0);
683 }
684 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685}
686
caryclarkaec25102015-04-29 08:28:30 -0700687void SkPath::setPt(int index, SkScalar x, SkScalar y) {
688 SkDEBUGCODE(this->validate();)
689
690 int count = fPathRef->countPoints();
691 if (count <= index) {
692 return;
693 } else {
694 SkPathRef::Editor ed(&fPathRef);
695 ed.atPoint(index)->set(x, y);
696 }
697}
698
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699void SkPath::setLastPt(SkScalar x, SkScalar y) {
700 SkDEBUGCODE(this->validate();)
701
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000702 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 if (count == 0) {
704 this->moveTo(x, y);
705 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000706 SkPathRef::Editor ed(&fPathRef);
707 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 }
709}
710
reed@google.com04863fa2011-05-15 04:08:24 +0000711void SkPath::setConvexity(Convexity c) {
712 if (fConvexity != c) {
713 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000714 }
715}
716
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717//////////////////////////////////////////////////////////////////////////////
718// Construction methods
719
reed026beb52015-06-10 14:23:15 -0700720#define DIRTY_AFTER_EDIT \
721 do { \
722 fConvexity = kUnknown_Convexity; \
723 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000724 } while (0)
725
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726void SkPath::incReserve(U16CPU inc) {
727 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000728 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729 SkDEBUGCODE(this->validate();)
730}
731
732void SkPath::moveTo(SkScalar x, SkScalar y) {
733 SkDEBUGCODE(this->validate();)
734
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000735 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000737 // remember our index
738 fLastMoveToIndex = fPathRef->countPoints();
739
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700741
742 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743}
744
745void SkPath::rMoveTo(SkScalar x, SkScalar y) {
746 SkPoint pt;
747 this->getLastPt(&pt);
748 this->moveTo(pt.fX + x, pt.fY + y);
749}
750
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000751void SkPath::injectMoveToIfNeeded() {
752 if (fLastMoveToIndex < 0) {
753 SkScalar x, y;
754 if (fPathRef->countVerbs() == 0) {
755 x = y = 0;
756 } else {
757 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
758 x = pt.fX;
759 y = pt.fY;
760 }
761 this->moveTo(x, y);
762 }
763}
764
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765void SkPath::lineTo(SkScalar x, SkScalar y) {
766 SkDEBUGCODE(this->validate();)
767
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000768 this->injectMoveToIfNeeded();
769
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000770 SkPathRef::Editor ed(&fPathRef);
771 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772
reed@google.comb54455e2011-05-16 14:16:04 +0000773 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000774}
775
776void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000777 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778 SkPoint pt;
779 this->getLastPt(&pt);
780 this->lineTo(pt.fX + x, pt.fY + y);
781}
782
783void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
784 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000785
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000786 this->injectMoveToIfNeeded();
787
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000788 SkPathRef::Editor ed(&fPathRef);
789 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790 pts[0].set(x1, y1);
791 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000792
reed@google.comb54455e2011-05-16 14:16:04 +0000793 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794}
795
796void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000797 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 SkPoint pt;
799 this->getLastPt(&pt);
800 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
801}
802
reed@google.com277c3f82013-05-31 15:17:50 +0000803void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
804 SkScalar w) {
805 // check for <= 0 or NaN with this test
806 if (!(w > 0)) {
807 this->lineTo(x2, y2);
808 } else if (!SkScalarIsFinite(w)) {
809 this->lineTo(x1, y1);
810 this->lineTo(x2, y2);
811 } else if (SK_Scalar1 == w) {
812 this->quadTo(x1, y1, x2, y2);
813 } else {
814 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000815
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000816 this->injectMoveToIfNeeded();
817
reed@google.com277c3f82013-05-31 15:17:50 +0000818 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000819 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000820 pts[0].set(x1, y1);
821 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000822
reed@google.com277c3f82013-05-31 15:17:50 +0000823 DIRTY_AFTER_EDIT;
824 }
825}
826
827void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
828 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000829 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000830 SkPoint pt;
831 this->getLastPt(&pt);
832 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
833}
834
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
836 SkScalar x3, SkScalar y3) {
837 SkDEBUGCODE(this->validate();)
838
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839 this->injectMoveToIfNeeded();
840
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000841 SkPathRef::Editor ed(&fPathRef);
842 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000843 pts[0].set(x1, y1);
844 pts[1].set(x2, y2);
845 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846
reed@google.comb54455e2011-05-16 14:16:04 +0000847 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848}
849
850void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
851 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000852 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 SkPoint pt;
854 this->getLastPt(&pt);
855 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
856 pt.fX + x3, pt.fY + y3);
857}
858
859void SkPath::close() {
860 SkDEBUGCODE(this->validate();)
861
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000862 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000864 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 case kLine_Verb:
866 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000867 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 case kMove_Verb: {
870 SkPathRef::Editor ed(&fPathRef);
871 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000873 }
reed@google.com277c3f82013-05-31 15:17:50 +0000874 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000875 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000876 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000877 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000878 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000879 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 }
881 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000882
883 // signal that we need a moveTo to follow us (unless we're done)
884#if 0
885 if (fLastMoveToIndex >= 0) {
886 fLastMoveToIndex = ~fLastMoveToIndex;
887 }
888#else
889 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
890#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000891}
892
893///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000894
fmalitac08d53e2015-11-17 09:53:29 -0800895namespace {
896
897template <unsigned N>
898class PointIterator {
899public:
900 PointIterator(SkPath::Direction dir, unsigned startIndex)
901 : fCurrent(startIndex % N)
902 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
903
904 const SkPoint& current() const {
905 SkASSERT(fCurrent < N);
906 return fPts[fCurrent];
907 }
908
909 const SkPoint& next() {
910 fCurrent = (fCurrent + fAdvance) % N;
911 return this->current();
912 }
913
914protected:
915 SkPoint fPts[N];
916
917private:
918 unsigned fCurrent;
919 unsigned fAdvance;
920};
921
922class RectPointIterator : public PointIterator<4> {
923public:
924 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
925 : PointIterator(dir, startIndex) {
926
927 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
928 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
929 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
930 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
931 }
932};
933
934class OvalPointIterator : public PointIterator<4> {
935public:
936 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
937 : PointIterator(dir, startIndex) {
938
939 const SkScalar cx = oval.centerX();
940 const SkScalar cy = oval.centerY();
941
942 fPts[0] = SkPoint::Make(cx, oval.fTop);
943 fPts[1] = SkPoint::Make(oval.fRight, cy);
944 fPts[2] = SkPoint::Make(cx, oval.fBottom);
945 fPts[3] = SkPoint::Make(oval.fLeft, cy);
946 }
947};
948
949class RRectPointIterator : public PointIterator<8> {
950public:
951 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
952 : PointIterator(dir, startIndex) {
953
954 const SkRect& bounds = rrect.getBounds();
955 const SkScalar L = bounds.fLeft;
956 const SkScalar T = bounds.fTop;
957 const SkScalar R = bounds.fRight;
958 const SkScalar B = bounds.fBottom;
959
960 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
961 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
962 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
963 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
964 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
965 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
966 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
967 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
968 }
969};
970
971} // anonymous namespace
972
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000973static void assert_known_direction(int dir) {
974 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
975}
976
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800978 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979}
980
981void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
982 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800983 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
984}
985
986void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000987 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700988 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800989 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000990 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800991 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000992
fmalitac08d53e2015-11-17 09:53:29 -0800993 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994
fmalitac08d53e2015-11-17 09:53:29 -0800995 const int kVerbs = 5; // moveTo + 3x lineTo + close
996 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997
fmalitac08d53e2015-11-17 09:53:29 -0800998 RectPointIterator iter(rect, dir, startIndex);
999
1000 this->moveTo(iter.current());
1001 this->lineTo(iter.next());
1002 this->lineTo(iter.next());
1003 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001005
1006 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007}
1008
reed@google.com744faba2012-05-29 19:54:52 +00001009void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1010 SkDEBUGCODE(this->validate();)
1011 if (count <= 0) {
1012 return;
1013 }
1014
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001015 fLastMoveToIndex = fPathRef->countPoints();
1016
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001017 // +close makes room for the extra kClose_Verb
1018 SkPathRef::Editor ed(&fPathRef, count+close, count);
1019
1020 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001021 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001022 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1023 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001024 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001025
reed@google.com744faba2012-05-29 19:54:52 +00001026 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001027 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001028 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001029 }
1030
reed@google.com744faba2012-05-29 19:54:52 +00001031 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001032 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001033}
1034
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001035#include "SkGeometry.h"
1036
reedf90ea012015-01-29 12:03:58 -08001037static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1038 SkPoint* pt) {
1039 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001040 // Chrome uses this path to move into and out of ovals. If not
1041 // treated as a special case the moves can distort the oval's
1042 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001043 pt->set(oval.fRight, oval.centerY());
1044 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045 } else if (0 == oval.width() && 0 == oval.height()) {
1046 // Chrome will sometimes create 0 radius round rects. Having degenerate
1047 // quad segments in the path prevents the path from being recognized as
1048 // a rect.
1049 // TODO: optimizing the case where only one of width or height is zero
1050 // should also be considered. This case, however, doesn't seem to be
1051 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001052 pt->set(oval.fRight, oval.fTop);
1053 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001054 }
reedf90ea012015-01-29 12:03:58 -08001055 return false;
1056}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001057
reedd5d27d92015-02-09 13:54:43 -08001058// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1059//
1060static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1061 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1062 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1063 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
1065 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001066 loss in radians-conversion and/or sin/cos, we may end up with coincident
1067 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1068 of drawing a nearly complete circle (good).
1069 e.g. canvas.drawArc(0, 359.99, ...)
1070 -vs- canvas.drawArc(0, 359.9, ...)
1071 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072 */
reedd5d27d92015-02-09 13:54:43 -08001073 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001074 SkScalar sw = SkScalarAbs(sweepAngle);
1075 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1076 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1077 // make a guess at a tiny angle (in radians) to tweak by
1078 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1079 // not sure how much will be enough, so we use a loop
1080 do {
1081 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001082 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1083 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084 }
1085 }
reedd5d27d92015-02-09 13:54:43 -08001086 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1087}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001088
reed9e779d42015-02-17 11:43:14 -08001089/**
1090 * If this returns 0, then the caller should just line-to the singlePt, else it should
1091 * ignore singlePt and append the specified number of conics.
1092 */
reedd5d27d92015-02-09 13:54:43 -08001093static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001094 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1095 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001096 SkMatrix matrix;
1097
1098 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1099 matrix.postTranslate(oval.centerX(), oval.centerY());
1100
reed9e779d42015-02-17 11:43:14 -08001101 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1102 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001103 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001104 }
1105 return count;
reedd5d27d92015-02-09 13:54:43 -08001106}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001107
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001108void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001109 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110 SkRRect rrect;
1111 rrect.setRectRadii(rect, (const SkVector*) radii);
1112 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113}
1114
reed@google.com4ed0fb72012-12-12 20:48:18 +00001115void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001116 // legacy start indices: 6 (CW) and 7(CCW)
1117 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1118}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001119
fmalitac08d53e2015-11-17 09:53:29 -08001120void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1121 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122
fmalitac08d53e2015-11-17 09:53:29 -08001123 if (rrect.isEmpty()) {
1124 return;
reed1b28a3a2014-12-17 14:42:09 -08001125 }
fmalitac08d53e2015-11-17 09:53:29 -08001126
caryclarkda707bf2015-11-19 14:47:43 -08001127 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001128 const SkRect& bounds = rrect.getBounds();
1129
1130 if (rrect.isRect()) {
1131 // degenerate(rect) => radii points are collapsing
1132 this->addRect(bounds, dir, (startIndex + 1) / 2);
1133 } else if (rrect.isOval()) {
1134 // degenerate(oval) => line points are collapsing
1135 this->addOval(bounds, dir, startIndex / 2);
1136 } else {
1137 fFirstDirection = this->hasOnlyMoveTos() ?
1138 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1139
1140 SkAutoPathBoundsUpdate apbu(this, bounds);
1141 SkAutoDisableDirectionCheck addc(this);
1142
1143 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1144 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1145 const SkScalar weight = SK_ScalarRoot2Over2;
1146
1147 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1148 const int kVerbs = startsWithConic
1149 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1150 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1151 this->incReserve(kVerbs);
1152
1153 RRectPointIterator rrectIter(rrect, dir, startIndex);
1154 // Corner iterator indices follow the collapsed radii model,
1155 // adjusted such that the start pt is "behind" the radii start pt.
1156 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1157 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1158
1159 this->moveTo(rrectIter.current());
1160 if (startsWithConic) {
1161 for (unsigned i = 0; i < 3; ++i) {
1162 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1163 this->lineTo(rrectIter.next());
1164 }
1165 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1166 // final lineTo handled by close().
1167 } else {
1168 for (unsigned i = 0; i < 4; ++i) {
1169 this->lineTo(rrectIter.next());
1170 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1171 }
1172 }
1173 this->close();
1174
caryclarkda707bf2015-11-19 14:47:43 -08001175 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001176 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001177
fmalitac08d53e2015-11-17 09:53:29 -08001178 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1179 }
1180
1181 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001182}
1183
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001184bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001185 int count = fPathRef->countVerbs();
1186 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1187 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001188 if (*verbs == kLine_Verb ||
1189 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001190 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191 *verbs == kCubic_Verb) {
1192 return false;
1193 }
1194 ++verbs;
1195 }
1196 return true;
1197}
1198
Brian Osmana2318572017-07-10 15:09:26 -04001199bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1200 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001201 if (count < 2) {
1202 return true;
1203 }
Brian Osmana2318572017-07-10 15:09:26 -04001204 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001205 const SkPoint& first = *pts;
1206 for (int index = 1; index < count; ++index) {
1207 if (first != pts[index]) {
1208 return false;
1209 }
1210 }
1211 return true;
1212}
1213
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001214void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1215 Direction dir) {
1216 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001217
humper@google.com75e3ca12013-04-08 21:44:11 +00001218 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001219 return;
1220 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001221
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001222 SkRRect rrect;
1223 rrect.setRectXY(rect, rx, ry);
1224 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001225}
1226
reed@android.com8a1c16f2008-12-17 15:59:43 +00001227void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001228 // legacy start index: 1
1229 this->addOval(oval, dir, 1);
1230}
1231
1232void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001233 assert_known_direction(dir);
1234
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001235 /* If addOval() is called after previous moveTo(),
1236 this path is still marked as an oval. This is used to
1237 fit into WebKit's calling sequences.
1238 We can't simply check isEmpty() in this case, as additional
1239 moveTo() would mark the path non empty.
1240 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001241 bool isOval = hasOnlyMoveTos();
1242 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001243 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001244 } else {
reed026beb52015-06-10 14:23:15 -07001245 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001246 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001247
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001248 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkAutoPathBoundsUpdate apbu(this, oval);
1250
fmalitac08d53e2015-11-17 09:53:29 -08001251 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1252 const int kVerbs = 6; // moveTo + 4x conicTo + close
1253 this->incReserve(kVerbs);
1254
1255 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1256 // The corner iterator pts are tracking "behind" the oval/radii pts.
1257 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001258 const SkScalar weight = SK_ScalarRoot2Over2;
1259
fmalitac08d53e2015-11-17 09:53:29 -08001260 this->moveTo(ovalIter.current());
1261 for (unsigned i = 0; i < 4; ++i) {
1262 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001263 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265
fmalitac08d53e2015-11-17 09:53:29 -08001266 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1267
robertphillips@google.com466310d2013-12-03 16:43:54 +00001268 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001269
bsalomon78d58d12016-05-27 09:17:04 -07001270 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001271}
1272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1274 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001275 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 }
1277}
1278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1280 bool forceMoveTo) {
1281 if (oval.width() < 0 || oval.height() < 0) {
1282 return;
1283 }
1284
reedf90ea012015-01-29 12:03:58 -08001285 if (fPathRef->countVerbs() == 0) {
1286 forceMoveTo = true;
1287 }
1288
1289 SkPoint lonePt;
1290 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1291 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1292 return;
1293 }
1294
reedd5d27d92015-02-09 13:54:43 -08001295 SkVector startV, stopV;
1296 SkRotationDirection dir;
1297 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1298
reed9e779d42015-02-17 11:43:14 -08001299 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001300
1301 // At this point, we know that the arc is not a lone point, but startV == stopV
1302 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1303 // cannot handle it.
1304 if (startV == stopV) {
1305 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1306 SkScalar radiusX = oval.width() / 2;
1307 SkScalar radiusY = oval.height() / 2;
1308 // We cannot use SkScalarSinCos function in the next line because
1309 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1310 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001311 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001312 // make sin(endAngle) to be 0 which will then draw a dot.
1313 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1314 oval.centerY() + radiusY * sk_float_sin(endAngle));
1315 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1316 return;
1317 }
1318
reedd5d27d92015-02-09 13:54:43 -08001319 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001320 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001321 if (count) {
1322 this->incReserve(count * 2 + 1);
1323 const SkPoint& pt = conics[0].fPts[0];
1324 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1325 for (int i = 0; i < count; ++i) {
1326 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1327 }
reed9e779d42015-02-17 11:43:14 -08001328 } else {
1329 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001330 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331}
1332
caryclark55d49052016-01-23 05:07:04 -08001333// This converts the SVG arc to conics.
1334// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1335// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1336// See also SVG implementation notes:
1337// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1338// Note that arcSweep bool value is flipped from the original implementation.
1339void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1340 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001341 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001342 SkPoint srcPts[2];
1343 this->getLastPt(&srcPts[0]);
1344 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1345 // joining the endpoints.
1346 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1347 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001348 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001349 return;
1350 }
1351 // If the current point and target point for the arc are identical, it should be treated as a
1352 // zero length path. This ensures continuity in animations.
1353 srcPts[1].set(x, y);
1354 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001355 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001356 return;
1357 }
1358 rx = SkScalarAbs(rx);
1359 ry = SkScalarAbs(ry);
1360 SkVector midPointDistance = srcPts[0] - srcPts[1];
1361 midPointDistance *= 0.5f;
1362
1363 SkMatrix pointTransform;
1364 pointTransform.setRotate(-angle);
1365
1366 SkPoint transformedMidPoint;
1367 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1368 SkScalar squareRx = rx * rx;
1369 SkScalar squareRy = ry * ry;
1370 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1371 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1372
1373 // Check if the radii are big enough to draw the arc, scale radii if not.
1374 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1375 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1376 if (radiiScale > 1) {
1377 radiiScale = SkScalarSqrt(radiiScale);
1378 rx *= radiiScale;
1379 ry *= radiiScale;
1380 }
1381
1382 pointTransform.setScale(1 / rx, 1 / ry);
1383 pointTransform.preRotate(-angle);
1384
1385 SkPoint unitPts[2];
1386 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1387 SkVector delta = unitPts[1] - unitPts[0];
1388
1389 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1390 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1391
1392 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1393 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1394 scaleFactor = -scaleFactor;
1395 }
1396 delta.scale(scaleFactor);
1397 SkPoint centerPoint = unitPts[0] + unitPts[1];
1398 centerPoint *= 0.5f;
1399 centerPoint.offset(-delta.fY, delta.fX);
1400 unitPts[0] -= centerPoint;
1401 unitPts[1] -= centerPoint;
1402 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1403 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1404 SkScalar thetaArc = theta2 - theta1;
1405 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1406 thetaArc += SK_ScalarPI * 2;
1407 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1408 thetaArc -= SK_ScalarPI * 2;
1409 }
1410 pointTransform.setRotate(angle);
1411 pointTransform.preScale(rx, ry);
1412
1413 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1414 SkScalar thetaWidth = thetaArc / segments;
1415 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1416 if (!SkScalarIsFinite(t)) {
1417 return;
1418 }
1419 SkScalar startTheta = theta1;
1420 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1421 for (int i = 0; i < segments; ++i) {
1422 SkScalar endTheta = startTheta + thetaWidth;
1423 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1424
1425 unitPts[1].set(cosEndTheta, sinEndTheta);
1426 unitPts[1] += centerPoint;
1427 unitPts[0] = unitPts[1];
1428 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1429 SkPoint mapped[2];
1430 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1431 this->conicTo(mapped[0], mapped[1], w);
1432 startTheta = endTheta;
1433 }
1434}
1435
1436void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1437 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1438 SkPoint currentPoint;
1439 this->getLastPt(&currentPoint);
1440 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1441}
1442
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001443void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444 if (oval.isEmpty() || 0 == sweepAngle) {
1445 return;
1446 }
1447
1448 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1449
1450 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001451 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1452 // See SkPath::addOval() docs.
1453 SkScalar startOver90 = startAngle / 90.f;
1454 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1455 SkScalar error = startOver90 - startOver90I;
1456 if (SkScalarNearlyEqual(error, 0)) {
1457 // Index 1 is at startAngle == 0.
1458 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1459 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1460 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1461 (unsigned) startIndex);
1462 return;
1463 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001464 }
bsalomon1978ce22016-05-31 10:42:16 -07001465 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466}
1467
1468/*
1469 Need to handle the case when the angle is sharp, and our computed end-points
1470 for the arc go behind pt1 and/or p2...
1471*/
reedc7789042015-01-29 12:59:11 -08001472void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001473 if (radius == 0) {
1474 this->lineTo(x1, y1);
1475 return;
1476 }
1477
1478 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001479
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 // need to know our prev pt so we can construct tangent vectors
1481 {
1482 SkPoint start;
1483 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001484 // Handle degenerate cases by adding a line to the first point and
1485 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 before.setNormalize(x1 - start.fX, y1 - start.fY);
1487 after.setNormalize(x2 - x1, y2 - y1);
1488 }
reed@google.comabf15c12011-01-18 20:35:51 +00001489
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 SkScalar cosh = SkPoint::DotProduct(before, after);
1491 SkScalar sinh = SkPoint::CrossProduct(before, after);
1492
1493 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001494 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 return;
1496 }
reed@google.comabf15c12011-01-18 20:35:51 +00001497
Mike Reeda99b6ce2017-02-04 11:04:26 -05001498 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499
Mike Reeda99b6ce2017-02-04 11:04:26 -05001500 SkScalar xx = x1 - dist * before.fX;
1501 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001502 after.setLength(dist);
1503 this->lineTo(xx, yy);
1504 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1505 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506}
1507
1508///////////////////////////////////////////////////////////////////////////////
1509
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001510void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 SkMatrix matrix;
1512
1513 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001514 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515}
1516
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001517void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001518 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001520 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 SkPoint pts[4];
1522 Verb verb;
1523
1524 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001525 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 while ((verb = iter.next(pts)) != kDone_Verb) {
1527 switch (verb) {
1528 case kMove_Verb:
1529 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001530 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1531 injectMoveToIfNeeded(); // In case last contour is closed
1532 this->lineTo(pts[0]);
1533 } else {
1534 this->moveTo(pts[0]);
1535 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 break;
1537 case kLine_Verb:
1538 proc(matrix, &pts[1], &pts[1], 1);
1539 this->lineTo(pts[1]);
1540 break;
1541 case kQuad_Verb:
1542 proc(matrix, &pts[1], &pts[1], 2);
1543 this->quadTo(pts[1], pts[2]);
1544 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001545 case kConic_Verb:
1546 proc(matrix, &pts[1], &pts[1], 2);
1547 this->conicTo(pts[1], pts[2], iter.conicWeight());
1548 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 case kCubic_Verb:
1550 proc(matrix, &pts[1], &pts[1], 3);
1551 this->cubicTo(pts[1], pts[2], pts[3]);
1552 break;
1553 case kClose_Verb:
1554 this->close();
1555 break;
1556 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001557 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001559 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 }
1561}
1562
1563///////////////////////////////////////////////////////////////////////////////
1564
reed@google.com277c3f82013-05-31 15:17:50 +00001565static int pts_in_verb(unsigned verb) {
1566 static const uint8_t gPtsInVerb[] = {
1567 1, // kMove
1568 1, // kLine
1569 2, // kQuad
1570 2, // kConic
1571 3, // kCubic
1572 0, // kClose
1573 0 // kDone
1574 };
1575
1576 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1577 return gPtsInVerb[verb];
1578}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580// ignore the last point of the 1st contour
1581void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001582 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1583 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 return;
1585 }
caryclark51c56782016-11-07 05:09:28 -08001586 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1587 SkASSERT(verbsEnd[0] == kMove_Verb);
1588 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1589 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590
caryclark51c56782016-11-07 05:09:28 -08001591 while (verbs < verbsEnd) {
1592 uint8_t v = *verbs++;
1593 pts -= pts_in_verb(v);
1594 switch (v) {
1595 case kMove_Verb:
1596 // if the path has multiple contours, stop after reversing the last
1597 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001599 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 break;
1601 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001602 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001604 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001605 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001606 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001608 this->cubicTo(pts[2], pts[1], pts[0]);
1609 break;
1610 case kClose_Verb:
1611 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 break;
1613 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001614 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615 break;
1616 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 }
1618}
1619
reed@google.com63d73742012-01-10 15:33:12 +00001620void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001621 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001622
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001623 const SkPoint* pts = src.fPathRef->pointsEnd();
1624 // we will iterator through src's verbs backwards
1625 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1626 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001627 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001628
1629 bool needMove = true;
1630 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001631 while (verbs < verbsEnd) {
1632 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001633 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001634
1635 if (needMove) {
1636 --pts;
1637 this->moveTo(pts->fX, pts->fY);
1638 needMove = false;
1639 }
1640 pts -= n;
1641 switch (v) {
1642 case kMove_Verb:
1643 if (needClose) {
1644 this->close();
1645 needClose = false;
1646 }
1647 needMove = true;
1648 pts += 1; // so we see the point in "if (needMove)" above
1649 break;
1650 case kLine_Verb:
1651 this->lineTo(pts[0]);
1652 break;
1653 case kQuad_Verb:
1654 this->quadTo(pts[1], pts[0]);
1655 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001656 case kConic_Verb:
1657 this->conicTo(pts[1], pts[0], *--conicWeights);
1658 break;
reed@google.com63d73742012-01-10 15:33:12 +00001659 case kCubic_Verb:
1660 this->cubicTo(pts[2], pts[1], pts[0]);
1661 break;
1662 case kClose_Verb:
1663 needClose = true;
1664 break;
1665 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001666 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001667 }
1668 }
1669}
1670
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671///////////////////////////////////////////////////////////////////////////////
1672
1673void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1674 SkMatrix matrix;
1675
1676 matrix.setTranslate(dx, dy);
1677 this->transform(matrix, dst);
1678}
1679
reed@android.com8a1c16f2008-12-17 15:59:43 +00001680static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1681 int level = 2) {
1682 if (--level >= 0) {
1683 SkPoint tmp[7];
1684
1685 SkChopCubicAtHalf(pts, tmp);
1686 subdivide_cubic_to(path, &tmp[0], level);
1687 subdivide_cubic_to(path, &tmp[3], level);
1688 } else {
1689 path->cubicTo(pts[1], pts[2], pts[3]);
1690 }
1691}
1692
1693void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1694 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001695 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 dst = (SkPath*)this;
1697 }
1698
tomhudson@google.com8d430182011-06-06 19:11:19 +00001699 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 SkPath tmp;
1701 tmp.fFillType = fFillType;
1702
1703 SkPath::Iter iter(*this, false);
1704 SkPoint pts[4];
1705 SkPath::Verb verb;
1706
reed@google.com4a3b7142012-05-16 17:16:46 +00001707 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708 switch (verb) {
1709 case kMove_Verb:
1710 tmp.moveTo(pts[0]);
1711 break;
1712 case kLine_Verb:
1713 tmp.lineTo(pts[1]);
1714 break;
1715 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001716 // promote the quad to a conic
1717 tmp.conicTo(pts[1], pts[2],
1718 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001720 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001721 tmp.conicTo(pts[1], pts[2],
1722 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001723 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724 case kCubic_Verb:
1725 subdivide_cubic_to(&tmp, pts);
1726 break;
1727 case kClose_Verb:
1728 tmp.close();
1729 break;
1730 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001731 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 break;
1733 }
1734 }
1735
1736 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001737 SkPathRef::Editor ed(&dst->fPathRef);
1738 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001739 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001741 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1742
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 dst->fFillType = fFillType;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001745 dst->fConvexity = fConvexity;
jvanverthb3eb6872014-10-24 07:12:51 -07001746 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001748
reed026beb52015-06-10 14:23:15 -07001749 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1750 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001751 } else {
1752 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001753 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1754 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001755 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001756 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1757 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001758 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001759 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001760 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001761 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001762 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001763 }
1764 }
1765
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 SkDEBUGCODE(dst->validate();)
1767 }
1768}
1769
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770///////////////////////////////////////////////////////////////////////////////
1771///////////////////////////////////////////////////////////////////////////////
1772
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001773enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001774 kEmptyContour_SegmentState, // The current contour is empty. We may be
1775 // starting processing or we may have just
1776 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001777 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1778 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1779 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780};
1781
1782SkPath::Iter::Iter() {
1783#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001784 fPts = nullptr;
1785 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001787 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001788 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789#endif
1790 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001791 fVerbs = nullptr;
1792 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 fNeedClose = false;
1794}
1795
1796SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1797 this->setPath(path, forceClose);
1798}
1799
1800void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801 fPts = path.fPathRef->points();
1802 fVerbs = path.fPathRef->verbs();
1803 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001804 fConicWeights = path.fPathRef->conicWeights();
1805 if (fConicWeights) {
1806 fConicWeights -= 1; // begin one behind
1807 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001808 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001809 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 fForceClose = SkToU8(forceClose);
1811 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001812 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813}
1814
1815bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001816 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 return false;
1818 }
1819 if (fForceClose) {
1820 return true;
1821 }
1822
1823 const uint8_t* verbs = fVerbs;
1824 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001825
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001826 if (kMove_Verb == *(verbs - 1)) {
1827 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 }
1829
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 while (verbs > stop) {
1831 // verbs points one beyond the current verb, decrement first.
1832 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 if (kMove_Verb == v) {
1834 break;
1835 }
1836 if (kClose_Verb == v) {
1837 return true;
1838 }
1839 }
1840 return false;
1841}
1842
1843SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001844 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001846 // A special case: if both points are NaN, SkPoint::operation== returns
1847 // false, but the iterator expects that they are treated as the same.
1848 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001849 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1850 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001851 return kClose_Verb;
1852 }
1853
reed@google.com9e25dbf2012-05-15 17:05:38 +00001854 pts[0] = fLastPt;
1855 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 fLastPt = fMoveTo;
1857 fCloseLine = true;
1858 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001859 } else {
1860 pts[0] = fMoveTo;
1861 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863}
1864
reed@google.com9e25dbf2012-05-15 17:05:38 +00001865const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 if (fSegmentState == kAfterMove_SegmentState) {
1867 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001868 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001869 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1872 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875}
1876
caryclarke8c56662015-07-14 11:19:26 -07001877void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 // We need to step over anything that will not move the current draw point
1879 // forward before the next move is seen
1880 const uint8_t* lastMoveVerb = 0;
1881 const SkPoint* lastMovePt = 0;
halcanary96fcdcc2015-08-27 07:41:13 -07001882 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 SkPoint lastPt = fLastPt;
1884 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 switch (verb) {
1887 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 // Keep a record of this most recent move
1889 lastMoveVerb = fVerbs;
1890 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001891 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001892 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001893 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 fPts++;
1895 break;
1896
1897 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001898 // A close when we are in a segment is always valid except when it
1899 // follows a move which follows a segment.
1900 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 return;
1902 }
1903 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 break;
1906
1907 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001908 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 if (lastMoveVerb) {
1910 fVerbs = lastMoveVerb;
1911 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001912 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 return;
1914 }
1915 return;
1916 }
1917 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001918 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 fPts++;
1920 break;
1921
reed@google.com277c3f82013-05-31 15:17:50 +00001922 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001924 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 if (lastMoveVerb) {
1926 fVerbs = lastMoveVerb;
1927 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001928 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 return;
1930 }
1931 return;
1932 }
1933 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001934 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001936 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 break;
1938
1939 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001940 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 if (lastMoveVerb) {
1942 fVerbs = lastMoveVerb;
1943 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001944 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 return;
1946 }
1947 return;
1948 }
1949 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fPts += 3;
1952 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001953
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001955 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 }
1957 }
1958}
1959
reed@google.com4a3b7142012-05-16 17:16:46 +00001960SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001961 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 // Close the curve if requested and if there is some curve to close
1965 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 return kLine_Verb;
1968 }
1969 fNeedClose = false;
1970 return kClose_Verb;
1971 }
1972 return kDone_Verb;
1973 }
1974
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 // fVerbs is one beyond the current verb, decrement first
1976 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001977 const SkPoint* SK_RESTRICT srcPts = fPts;
1978 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001979
1980 switch (verb) {
1981 case kMove_Verb:
1982 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001983 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 verb = this->autoClose(pts);
1985 if (verb == kClose_Verb) {
1986 fNeedClose = false;
1987 }
1988 return (Verb)verb;
1989 }
1990 if (fVerbs == fVerbStop) { // might be a trailing moveto
1991 return kDone_Verb;
1992 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001993 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001994 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001996 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001998 fNeedClose = fForceClose;
1999 break;
2000 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002001 pts[0] = this->cons_moveTo();
2002 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 fLastPt = srcPts[0];
2004 fCloseLine = false;
2005 srcPts += 1;
2006 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002007 case kConic_Verb:
2008 fConicWeights += 1;
2009 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002011 pts[0] = this->cons_moveTo();
2012 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 fLastPt = srcPts[1];
2014 srcPts += 2;
2015 break;
2016 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002017 pts[0] = this->cons_moveTo();
2018 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 fLastPt = srcPts[2];
2020 srcPts += 3;
2021 break;
2022 case kClose_Verb:
2023 verb = this->autoClose(pts);
2024 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002025 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 } else {
2027 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002028 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002030 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 break;
2032 }
2033 fPts = srcPts;
2034 return (Verb)verb;
2035}
2036
2037///////////////////////////////////////////////////////////////////////////////
2038
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002040 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041*/
2042
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002043size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 SkDEBUGCODE(this->validate();)
2045
halcanary96fcdcc2015-08-27 07:41:13 -07002046 if (nullptr == storage) {
caryclark69006412016-02-17 12:16:27 -08002047 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002048 return SkAlign4(byteCount);
2049 }
2050
2051 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002052
robertphillips@google.com466310d2013-12-03 16:43:54 +00002053 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002054 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002055 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002056 (fIsVolatile << kIsVolatile_SerializationShift) |
2057 kCurrent_Version;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002059 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002060 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002061
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002062 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002063
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002064 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002065 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066}
2067
Mike Reed41a930f2017-07-26 17:33:44 -04002068sk_sp<SkData> SkPath::serialize() const {
2069 size_t size = this->writeToMemory(nullptr);
2070 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2071 this->writeToMemory(data->writable_data());
2072 return data;
2073}
2074
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002075size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002076 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002077
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002078 int32_t packed;
2079 if (!buffer.readS32(&packed)) {
2080 return 0;
2081 }
2082
reed8f086022015-06-11 14:22:19 -07002083 unsigned version = packed & 0xFF;
caryclark69006412016-02-17 12:16:27 -08002084 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2085 return 0;
2086 }
mtklein1b249332015-07-07 12:21:21 -07002087
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002088 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
robertphillips7070a3c2016-06-28 04:54:54 -07002089 fFillType = (packed >> kFillType_SerializationShift) & 0x3;
reed8f086022015-06-11 14:22:19 -07002090 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
jvanverthb3eb6872014-10-24 07:12:51 -07002091 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002092 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002093 if (!pathRef) {
2094 return 0;
2095 }
2096
2097 fPathRef.reset(pathRef);
2098 SkDEBUGCODE(this->validate();)
2099 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002100
reed8f086022015-06-11 14:22:19 -07002101 // compatibility check
2102 if (version < kPathPrivFirstDirection_Version) {
2103 switch (dir) { // old values
2104 case 0:
2105 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2106 break;
2107 case 1:
2108 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2109 break;
2110 case 2:
2111 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2112 break;
2113 default:
2114 SkASSERT(false);
2115 }
2116 } else {
2117 fFirstDirection = dir;
2118 }
2119
ajumaf8aec582016-01-13 13:46:31 -08002120 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121}
2122
2123///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124
Ben Wagner4d1955c2017-03-10 13:08:15 -05002125#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002126#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002127#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002128
reed@google.com51bbe752013-01-17 22:07:50 +00002129static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002130 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002131 str->append(label);
2132 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002133
reed@google.com51bbe752013-01-17 22:07:50 +00002134 const SkScalar* values = &pts[0].fX;
2135 count *= 2;
2136
2137 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002138 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002139 if (i < count - 1) {
2140 str->append(", ");
2141 }
2142 }
reed@google.com277c3f82013-05-31 15:17:50 +00002143 if (conicWeight >= 0) {
2144 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002145 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002146 }
caryclark08fa28c2014-10-23 13:08:56 -07002147 str->append(");");
reede05fed02014-12-15 07:59:53 -08002148 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002149 str->append(" // ");
2150 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002151 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002152 if (i < count - 1) {
2153 str->append(", ");
2154 }
2155 }
2156 if (conicWeight >= 0) {
2157 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002158 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002159 }
2160 }
2161 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002162}
2163
caryclarke9562592014-09-15 09:26:09 -07002164void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002165 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 Iter iter(*this, forceClose);
2167 SkPoint pts[4];
2168 Verb verb;
2169
reed@google.com51bbe752013-01-17 22:07:50 +00002170 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002171 char const * const gFillTypeStrs[] = {
2172 "Winding",
2173 "EvenOdd",
2174 "InverseWinding",
2175 "InverseEvenOdd",
2176 };
2177 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2178 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002179 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180 switch (verb) {
2181 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002182 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 break;
2184 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002185 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 break;
2187 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002188 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002189 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002190 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002191 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002192 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002194 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 break;
2196 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002197 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002198 break;
2199 default:
2200 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2201 verb = kDone_Verb; // stop the loop
2202 break;
2203 }
caryclark1049f122015-04-20 08:31:59 -07002204 if (!wStream && builder.size()) {
2205 SkDebugf("%s", builder.c_str());
2206 builder.reset();
2207 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208 }
caryclark66a5d8b2014-06-24 08:30:15 -07002209 if (wStream) {
2210 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002211 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212}
2213
reed@android.come522ca52009-11-23 20:10:41 +00002214void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002215 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002216}
2217
2218void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002219 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002220}
2221
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002222
2223bool SkPath::isValid() const {
2224 if ((fFillType & ~3) != 0) {
2225 return false;
2226 }
reed@google.comabf15c12011-01-18 20:35:51 +00002227
djsollen@google.com077348c2012-10-22 20:23:32 +00002228#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002229 if (!fBoundsIsDirty) {
2230 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002231
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002232 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002233 if (SkToBool(fIsFinite) != isFinite) {
2234 return false;
2235 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002236
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002237 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002238 // if we're empty, fBounds may be empty but translated, so we can't
2239 // necessarily compare to bounds directly
2240 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2241 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002242 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2243 return false;
2244 }
reed@android.come522ca52009-11-23 20:10:41 +00002245 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002246 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002247 if (!fBounds.isEmpty()) {
2248 return false;
2249 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002250 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002251 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002252 if (!fBounds.contains(bounds)) {
2253 return false;
2254 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002255 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002256 }
reed@android.come522ca52009-11-23 20:10:41 +00002257 }
2258 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002259#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002260 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002261}
reed@android.come522ca52009-11-23 20:10:41 +00002262
reed@google.com04863fa2011-05-15 04:08:24 +00002263///////////////////////////////////////////////////////////////////////////////
2264
reed@google.com0b7b9822011-05-16 12:29:27 +00002265static int sign(SkScalar x) { return x < 0; }
2266#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002267
robertphillipsc506e302014-09-16 09:43:31 -07002268enum DirChange {
2269 kLeft_DirChange,
2270 kRight_DirChange,
2271 kStraight_DirChange,
2272 kBackwards_DirChange,
2273
2274 kInvalid_DirChange
2275};
2276
2277
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002278static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002279 // The error epsilon was empirically derived; worse case round rects
2280 // with a mid point outset by 2x float epsilon in tests had an error
2281 // of 12.
2282 const int epsilon = 16;
2283 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2284 return false;
2285 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002286 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002287 int aBits = SkFloatAs2sCompliment(compA);
2288 int bBits = SkFloatAs2sCompliment(compB);
2289 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002290}
2291
caryclarkb4216502015-03-02 13:02:34 -08002292static bool approximately_zero_when_compared_to(double x, double y) {
2293 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002294}
2295
caryclarkb4216502015-03-02 13:02:34 -08002296
reed@google.com04863fa2011-05-15 04:08:24 +00002297// only valid for a single contour
2298struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002299 Convexicator()
2300 : fPtCount(0)
2301 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002302 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002303 , fIsFinite(true)
2304 , fIsCurve(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002305 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002306 // warnings
djsollen2f124632016-04-29 13:53:05 -07002307 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002308 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002309 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002310 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002311 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002312
2313 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002314 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002315 }
2316
2317 SkPath::Convexity getConvexity() const { return fConvexity; }
2318
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002319 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002320 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002321
reed@google.com04863fa2011-05-15 04:08:24 +00002322 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002323 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002324 return;
2325 }
2326
2327 if (0 == fPtCount) {
2328 fCurrPt = pt;
2329 ++fPtCount;
2330 } else {
2331 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002332 SkScalar lengthSqd = vec.lengthSqd();
2333 if (!SkScalarIsFinite(lengthSqd)) {
2334 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002335 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002336 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002337 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002338 fCurrPt = pt;
2339 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002340 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002341 } else {
2342 SkASSERT(fPtCount > 2);
2343 this->addVec(vec);
2344 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002345
reed@google.com85b6e392011-05-15 20:25:17 +00002346 int sx = sign(vec.fX);
2347 int sy = sign(vec.fY);
2348 fDx += (sx != fSx);
2349 fDy += (sy != fSy);
2350 fSx = sx;
2351 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002352
reed@google.com85b6e392011-05-15 20:25:17 +00002353 if (fDx > 3 || fDy > 3) {
2354 fConvexity = SkPath::kConcave_Convexity;
2355 }
reed@google.com04863fa2011-05-15 04:08:24 +00002356 }
2357 }
2358 }
2359
2360 void close() {
2361 if (fPtCount > 2) {
2362 this->addVec(fFirstVec);
2363 }
2364 }
2365
caryclarkb4216502015-03-02 13:02:34 -08002366 DirChange directionChange(const SkVector& curVec) {
2367 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2368
2369 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2370 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2371 largest = SkTMax(largest, -smallest);
2372
2373 if (!almost_equal(largest, largest + cross)) {
2374 int sign = SkScalarSignAsInt(cross);
2375 if (sign) {
2376 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2377 }
2378 }
2379
2380 if (cross) {
2381 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2382 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2383 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2384 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2385 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2386 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2387 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2388 if (sign) {
2389 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2390 }
2391 }
2392 }
2393
2394 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2395 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2396 fLastVec.dot(curVec) < 0.0f) {
2397 return kBackwards_DirChange;
2398 }
2399
2400 return kStraight_DirChange;
2401 }
2402
2403
caryclarkd3d1a982014-12-08 04:57:38 -08002404 bool isFinite() const {
2405 return fIsFinite;
2406 }
2407
caryclark5ccef572015-03-02 10:07:56 -08002408 void setCurve(bool isCurve) {
2409 fIsCurve = isCurve;
2410 }
2411
reed@google.com04863fa2011-05-15 04:08:24 +00002412private:
2413 void addVec(const SkVector& vec) {
2414 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002415 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002416 switch (dir) {
2417 case kLeft_DirChange: // fall through
2418 case kRight_DirChange:
2419 if (kInvalid_DirChange == fExpectedDir) {
2420 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002421 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2422 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002423 } else if (dir != fExpectedDir) {
2424 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002425 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002426 }
2427 fLastVec = vec;
2428 break;
2429 case kStraight_DirChange:
2430 break;
2431 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002432 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002433 // If any of the subsequent dir is non-backward, it'll be concave.
2434 // Otherwise, it's still convex.
2435 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002436 }
robertphillipsc506e302014-09-16 09:43:31 -07002437 fLastVec = vec;
2438 break;
2439 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002440 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002441 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002442 }
2443 }
2444
caryclarkb4216502015-03-02 13:02:34 -08002445 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002446 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002447 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002448 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2449 // value with the current vec is deemed to be of a significant value.
2450 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002451 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002452 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002453 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002454 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002455 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002456 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002457 bool fIsCurve;
reed@google.com04863fa2011-05-15 04:08:24 +00002458};
2459
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002460SkPath::Convexity SkPath::internalGetConvexity() const {
2461 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002462 SkPoint pts[4];
2463 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002464 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002465
2466 int contourCount = 0;
2467 int count;
2468 Convexicator state;
2469
caryclarkd3d1a982014-12-08 04:57:38 -08002470 if (!isFinite()) {
2471 return kUnknown_Convexity;
2472 }
caryclarke8c56662015-07-14 11:19:26 -07002473 while ((verb = iter.next(pts, true, true)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002474 switch (verb) {
2475 case kMove_Verb:
2476 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002477 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002478 return kConcave_Convexity;
2479 }
2480 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002481 // fall through
2482 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002483 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002484 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002485 break;
caryclark5ccef572015-03-02 10:07:56 -08002486 case kQuad_Verb:
2487 // fall through
2488 case kConic_Verb:
2489 // fall through
2490 case kCubic_Verb:
2491 count = 2 + (kCubic_Verb == verb);
2492 // As an additional enhancement, this could set curve true only
2493 // if the curve is nonlinear
2494 state.setCurve(true);
2495 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002496 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002497 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002498 state.close();
2499 count = 0;
2500 break;
2501 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002502 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002503 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002504 return kConcave_Convexity;
2505 }
2506
2507 for (int i = 1; i <= count; i++) {
2508 state.addPt(pts[i]);
2509 }
2510 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002511 if (!state.isFinite()) {
2512 return kUnknown_Convexity;
2513 }
reed@google.com04863fa2011-05-15 04:08:24 +00002514 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002515 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002516 return kConcave_Convexity;
2517 }
2518 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002519 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002520 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
2521 fFirstDirection = state.getFirstDirection();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002522 }
2523 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002524}
reed@google.com69a99432012-01-10 18:00:10 +00002525
2526///////////////////////////////////////////////////////////////////////////////
2527
2528class ContourIter {
2529public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002531
2532 bool done() const { return fDone; }
2533 // if !done() then these may be called
2534 int count() const { return fCurrPtCount; }
2535 const SkPoint* pts() const { return fCurrPt; }
2536 void next();
2537
2538private:
2539 int fCurrPtCount;
2540 const SkPoint* fCurrPt;
2541 const uint8_t* fCurrVerb;
2542 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002543 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002544 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002545 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002546};
2547
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002548ContourIter::ContourIter(const SkPathRef& pathRef) {
2549 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002550 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002551 fCurrPt = pathRef.points();
2552 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002553 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002554 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002555 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002556 this->next();
2557}
2558
2559void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002560 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002561 fDone = true;
2562 }
2563 if (fDone) {
2564 return;
2565 }
2566
2567 // skip pts of prev contour
2568 fCurrPt += fCurrPtCount;
2569
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002570 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002571 int ptCount = 1; // moveTo
2572 const uint8_t* verbs = fCurrVerb;
2573
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002574 for (--verbs; verbs > fStopVerbs; --verbs) {
2575 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002576 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002577 goto CONTOUR_END;
2578 case SkPath::kLine_Verb:
2579 ptCount += 1;
2580 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002581 case SkPath::kConic_Verb:
2582 fCurrConicWeight += 1;
2583 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002584 case SkPath::kQuad_Verb:
2585 ptCount += 2;
2586 break;
2587 case SkPath::kCubic_Verb:
2588 ptCount += 3;
2589 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002590 case SkPath::kClose_Verb:
2591 break;
2592 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002593 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002594 break;
2595 }
2596 }
2597CONTOUR_END:
2598 fCurrPtCount = ptCount;
2599 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002600 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002601}
2602
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002603// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002604static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002605 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2606 // We may get 0 when the above subtracts underflow. We expect this to be
2607 // very rare and lazily promote to double.
2608 if (0 == cross) {
2609 double p0x = SkScalarToDouble(p0.fX);
2610 double p0y = SkScalarToDouble(p0.fY);
2611
2612 double p1x = SkScalarToDouble(p1.fX);
2613 double p1y = SkScalarToDouble(p1.fY);
2614
2615 double p2x = SkScalarToDouble(p2.fX);
2616 double p2y = SkScalarToDouble(p2.fY);
2617
2618 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2619 (p1y - p0y) * (p2x - p0x));
2620
2621 }
2622 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002623}
2624
reed@google.comc1ea60a2012-01-31 15:15:36 +00002625// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002626static int find_max_y(const SkPoint pts[], int count) {
2627 SkASSERT(count > 0);
2628 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002629 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002630 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002631 SkScalar y = pts[i].fY;
2632 if (y > max) {
2633 max = y;
2634 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002635 }
2636 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002637 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002638}
2639
reed@google.comcabaf1d2012-01-11 21:03:05 +00002640static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2641 int i = index;
2642 for (;;) {
2643 i = (i + inc) % n;
2644 if (i == index) { // we wrapped around, so abort
2645 break;
2646 }
2647 if (pts[index] != pts[i]) { // found a different point, success!
2648 break;
2649 }
2650 }
2651 return i;
2652}
2653
reed@google.comc1ea60a2012-01-31 15:15:36 +00002654/**
2655 * Starting at index, and moving forward (incrementing), find the xmin and
2656 * xmax of the contiguous points that have the same Y.
2657 */
2658static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2659 int* maxIndexPtr) {
2660 const SkScalar y = pts[index].fY;
2661 SkScalar min = pts[index].fX;
2662 SkScalar max = min;
2663 int minIndex = index;
2664 int maxIndex = index;
2665 for (int i = index + 1; i < n; ++i) {
2666 if (pts[i].fY != y) {
2667 break;
2668 }
2669 SkScalar x = pts[i].fX;
2670 if (x < min) {
2671 min = x;
2672 minIndex = i;
2673 } else if (x > max) {
2674 max = x;
2675 maxIndex = i;
2676 }
2677 }
2678 *maxIndexPtr = maxIndex;
2679 return minIndex;
2680}
2681
reed026beb52015-06-10 14:23:15 -07002682static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2683 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002684}
2685
reed@google.comac8543f2012-01-30 20:51:25 +00002686/*
2687 * We loop through all contours, and keep the computed cross-product of the
2688 * contour that contained the global y-max. If we just look at the first
2689 * contour, we may find one that is wound the opposite way (correctly) since
2690 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2691 * that is outer most (or at least has the global y-max) before we can consider
2692 * its cross product.
2693 */
reed026beb52015-06-10 14:23:15 -07002694bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002695 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2696 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002697 return true;
2698 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002699
2700 // don't want to pay the cost for computing this if it
2701 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002702 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2703 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002704 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002705 return false;
2706 }
reed@google.com69a99432012-01-10 18:00:10 +00002707
reed026beb52015-06-10 14:23:15 -07002708 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002709
reed@google.comac8543f2012-01-30 20:51:25 +00002710 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002711 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002712 SkScalar ymaxCross = 0;
2713
reed@google.com69a99432012-01-10 18:00:10 +00002714 for (; !iter.done(); iter.next()) {
2715 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002716 if (n < 3) {
2717 continue;
2718 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002719
reed@google.comcabaf1d2012-01-11 21:03:05 +00002720 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002721 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002722 int index = find_max_y(pts, n);
2723 if (pts[index].fY < ymax) {
2724 continue;
2725 }
2726
2727 // If there is more than 1 distinct point at the y-max, we take the
2728 // x-min and x-max of them and just subtract to compute the dir.
2729 if (pts[(index + 1) % n].fY == pts[index].fY) {
2730 int maxIndex;
2731 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2732 if (minIndex == maxIndex) {
2733 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002734 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002735 SkASSERT(pts[minIndex].fY == pts[index].fY);
2736 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2737 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2738 // we just subtract the indices, and let that auto-convert to
2739 // SkScalar, since we just want - or + to signal the direction.
2740 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002741 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002742 TRY_CROSSPROD:
2743 // Find a next and prev index to use for the cross-product test,
2744 // but we try to find pts that form non-zero vectors from pts[index]
2745 //
2746 // Its possible that we can't find two non-degenerate vectors, so
2747 // we have to guard our search (e.g. all the pts could be in the
2748 // same place).
2749
2750 // we pass n - 1 instead of -1 so we don't foul up % operator by
2751 // passing it a negative LH argument.
2752 int prev = find_diff_pt(pts, index, n, n - 1);
2753 if (prev == index) {
2754 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002755 continue;
2756 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002757 int next = find_diff_pt(pts, index, n, 1);
2758 SkASSERT(next != index);
2759 cross = cross_prod(pts[prev], pts[index], pts[next]);
2760 // if we get a zero and the points are horizontal, then we look at the spread in
2761 // x-direction. We really should continue to walk away from the degeneracy until
2762 // there is a divergence.
2763 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2764 // construct the subtract so we get the correct Direction below
2765 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002766 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002767 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002769 if (cross) {
2770 // record our best guess so far
2771 ymax = pts[index].fY;
2772 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002773 }
2774 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002775 if (ymaxCross) {
2776 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002777 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002778 return true;
2779 } else {
2780 return false;
2781 }
reed@google.comac8543f2012-01-30 20:51:25 +00002782}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783
2784///////////////////////////////////////////////////////////////////////////////
2785
caryclark9aacd902015-12-14 08:38:09 -08002786static bool between(SkScalar a, SkScalar b, SkScalar c) {
2787 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2788 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2789 return (a - b) * (c - b) <= 0;
2790}
2791
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2793 SkScalar t) {
2794 SkScalar A = c3 + 3*(c1 - c2) - c0;
2795 SkScalar B = 3*(c2 - c1 - c1 + c0);
2796 SkScalar C = 3*(c1 - c0);
2797 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002798 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799}
2800
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002801template <size_t N> static void find_minmax(const SkPoint pts[],
2802 SkScalar* minPtr, SkScalar* maxPtr) {
2803 SkScalar min, max;
2804 min = max = pts[0].fX;
2805 for (size_t i = 1; i < N; ++i) {
2806 min = SkMinScalar(min, pts[i].fX);
2807 max = SkMaxScalar(max, pts[i].fX);
2808 }
2809 *minPtr = min;
2810 *maxPtr = max;
2811}
2812
caryclark9cb5d752015-12-18 04:35:24 -08002813static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2814 if (start.fY == end.fY) {
2815 return between(start.fX, x, end.fX) && x != end.fX;
2816 } else {
2817 return x == start.fX && y == start.fY;
2818 }
2819}
2820
caryclark9aacd902015-12-14 08:38:09 -08002821static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002822 SkScalar y0 = pts[0].fY;
2823 SkScalar y3 = pts[3].fY;
2824
2825 int dir = 1;
2826 if (y0 > y3) {
2827 SkTSwap(y0, y3);
2828 dir = -1;
2829 }
2830 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 return 0;
2832 }
caryclark9cb5d752015-12-18 04:35:24 -08002833 if (checkOnCurve(x, y, pts[0], pts[3])) {
2834 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002835 return 0;
2836 }
caryclark9cb5d752015-12-18 04:35:24 -08002837 if (y == y3) {
2838 return 0;
2839 }
2840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 // quickreject or quickaccept
2842 SkScalar min, max;
2843 find_minmax<4>(pts, &min, &max);
2844 if (x < min) {
2845 return 0;
2846 }
2847 if (x > max) {
2848 return dir;
2849 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002850
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002851 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002852 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002853 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002854 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002855 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002856 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002857 if (SkScalarNearlyEqual(xt, x)) {
2858 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2859 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002860 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002861 }
2862 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 return xt < x ? dir : 0;
2864}
2865
caryclark9aacd902015-12-14 08:38:09 -08002866static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 SkPoint dst[10];
2868 int n = SkChopCubicAtYExtrema(pts, dst);
2869 int w = 0;
2870 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002871 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 }
2873 return w;
2874}
2875
caryclark9aacd902015-12-14 08:38:09 -08002876static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2877 SkASSERT(src);
2878 SkASSERT(t >= 0 && t <= 1);
2879 SkScalar src2w = src[2] * w;
2880 SkScalar C = src[0];
2881 SkScalar A = src[4] - 2 * src2w + C;
2882 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002883 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002884}
2885
2886
2887static double conic_eval_denominator(SkScalar w, SkScalar t) {
2888 SkScalar B = 2 * (w - 1);
2889 SkScalar C = 1;
2890 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002891 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002892}
2893
2894static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2895 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002896 SkScalar y0 = pts[0].fY;
2897 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002898
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002899 int dir = 1;
2900 if (y0 > y2) {
2901 SkTSwap(y0, y2);
2902 dir = -1;
2903 }
caryclark9aacd902015-12-14 08:38:09 -08002904 if (y < y0 || y > y2) {
2905 return 0;
2906 }
caryclark9cb5d752015-12-18 04:35:24 -08002907 if (checkOnCurve(x, y, pts[0], pts[2])) {
2908 *onCurveCount += 1;
2909 return 0;
2910 }
caryclark9aacd902015-12-14 08:38:09 -08002911 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002912 return 0;
2913 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002914
caryclark9aacd902015-12-14 08:38:09 -08002915 SkScalar roots[2];
2916 SkScalar A = pts[2].fY;
2917 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2918 SkScalar C = pts[0].fY;
2919 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2920 B -= C; // B = b*w - w * yCept + yCept - a
2921 C -= y;
2922 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2923 SkASSERT(n <= 1);
2924 SkScalar xt;
2925 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002926 // zero roots are returned only when y0 == y
2927 // Need [0] if dir == 1
2928 // and [2] if dir == -1
2929 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002930 } else {
2931 SkScalar t = roots[0];
2932 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2933 }
2934 if (SkScalarNearlyEqual(xt, x)) {
2935 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2936 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002937 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002938 }
2939 }
2940 return xt < x ? dir : 0;
2941}
2942
2943static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2944 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2945 if (y0 == y1) {
2946 return true;
2947 }
2948 if (y0 < y1) {
2949 return y1 <= y2;
2950 } else {
2951 return y1 >= y2;
2952 }
2953}
2954
2955static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2956 int* onCurveCount) {
2957 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002958 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002959 // If the data points are very large, the conic may not be monotonic but may also
2960 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002961 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2962 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2963 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002964 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2965 }
2966 return w;
2967}
2968
2969static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2970 SkScalar y0 = pts[0].fY;
2971 SkScalar y2 = pts[2].fY;
2972
2973 int dir = 1;
2974 if (y0 > y2) {
2975 SkTSwap(y0, y2);
2976 dir = -1;
2977 }
2978 if (y < y0 || y > y2) {
2979 return 0;
2980 }
caryclark9cb5d752015-12-18 04:35:24 -08002981 if (checkOnCurve(x, y, pts[0], pts[2])) {
2982 *onCurveCount += 1;
2983 return 0;
2984 }
caryclark9aacd902015-12-14 08:38:09 -08002985 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002986 return 0;
2987 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 // bounds check on X (not required. is it faster?)
2989#if 0
2990 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2991 return 0;
2992 }
2993#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002994
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 SkScalar roots[2];
2996 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2997 2 * (pts[1].fY - pts[0].fY),
2998 pts[0].fY - y,
2999 roots);
3000 SkASSERT(n <= 1);
3001 SkScalar xt;
3002 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003003 // zero roots are returned only when y0 == y
3004 // Need [0] if dir == 1
3005 // and [2] if dir == -1
3006 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 } else {
3008 SkScalar t = roots[0];
3009 SkScalar C = pts[0].fX;
3010 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3011 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003012 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 }
caryclark9aacd902015-12-14 08:38:09 -08003014 if (SkScalarNearlyEqual(xt, x)) {
3015 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3016 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003017 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003018 }
3019 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 return xt < x ? dir : 0;
3021}
3022
caryclark9aacd902015-12-14 08:38:09 -08003023static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003024 SkPoint dst[5];
3025 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003026
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003027 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3028 n = SkChopQuadAtYExtrema(pts, dst);
3029 pts = dst;
3030 }
caryclark9aacd902015-12-14 08:38:09 -08003031 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003032 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003033 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003034 }
3035 return w;
3036}
3037
caryclark9aacd902015-12-14 08:38:09 -08003038static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003039 SkScalar x0 = pts[0].fX;
3040 SkScalar y0 = pts[0].fY;
3041 SkScalar x1 = pts[1].fX;
3042 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003043
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003044 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003045
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003046 int dir = 1;
3047 if (y0 > y1) {
3048 SkTSwap(y0, y1);
3049 dir = -1;
3050 }
caryclark9aacd902015-12-14 08:38:09 -08003051 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003052 return 0;
3053 }
caryclark9cb5d752015-12-18 04:35:24 -08003054 if (checkOnCurve(x, y, pts[0], pts[1])) {
3055 *onCurveCount += 1;
3056 return 0;
3057 }
3058 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003059 return 0;
3060 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003061 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003062
caryclark9aacd902015-12-14 08:38:09 -08003063 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003064 // zero cross means the point is on the line, and since the case where
3065 // y of the query point is at the end point is handled above, we can be
3066 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003067 if (x != x1 || y != pts[1].fY) {
3068 *onCurveCount += 1;
3069 }
caryclark9aacd902015-12-14 08:38:09 -08003070 dir = 0;
3071 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003072 dir = 0;
3073 }
3074 return dir;
3075}
3076
caryclark9aacd902015-12-14 08:38:09 -08003077static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3078 SkTDArray<SkVector>* tangents) {
3079 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3080 && !between(pts[2].fY, y, pts[3].fY)) {
3081 return;
3082 }
3083 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3084 && !between(pts[2].fX, x, pts[3].fX)) {
3085 return;
3086 }
3087 SkPoint dst[10];
3088 int n = SkChopCubicAtYExtrema(pts, dst);
3089 for (int i = 0; i <= n; ++i) {
3090 SkPoint* c = &dst[i * 3];
3091 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003092 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003093 continue;
mbarbella276e6332016-05-31 14:44:01 -07003094 }
caryclark9aacd902015-12-14 08:38:09 -08003095 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3096 if (!SkScalarNearlyEqual(x, xt)) {
3097 continue;
3098 }
3099 SkVector tangent;
3100 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3101 tangents->push(tangent);
3102 }
3103}
3104
3105static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3106 SkTDArray<SkVector>* tangents) {
3107 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3108 return;
3109 }
3110 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3111 return;
3112 }
3113 SkScalar roots[2];
3114 SkScalar A = pts[2].fY;
3115 SkScalar B = pts[1].fY * w - y * w + y;
3116 SkScalar C = pts[0].fY;
3117 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3118 B -= C; // B = b*w - w * yCept + yCept - a
3119 C -= y;
3120 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3121 for (int index = 0; index < n; ++index) {
3122 SkScalar t = roots[index];
3123 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3124 if (!SkScalarNearlyEqual(x, xt)) {
3125 continue;
3126 }
3127 SkConic conic(pts, w);
3128 tangents->push(conic.evalTangentAt(t));
3129 }
3130}
3131
3132static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3133 SkTDArray<SkVector>* tangents) {
3134 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3135 return;
3136 }
3137 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3138 return;
3139 }
3140 SkScalar roots[2];
3141 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3142 2 * (pts[1].fY - pts[0].fY),
3143 pts[0].fY - y,
3144 roots);
3145 for (int index = 0; index < n; ++index) {
3146 SkScalar t = roots[index];
3147 SkScalar C = pts[0].fX;
3148 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3149 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003150 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003151 if (!SkScalarNearlyEqual(x, xt)) {
3152 continue;
3153 }
3154 tangents->push(SkEvalQuadTangentAt(pts, t));
3155 }
3156}
3157
3158static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3159 SkTDArray<SkVector>* tangents) {
3160 SkScalar y0 = pts[0].fY;
3161 SkScalar y1 = pts[1].fY;
3162 if (!between(y0, y, y1)) {
3163 return;
3164 }
3165 SkScalar x0 = pts[0].fX;
3166 SkScalar x1 = pts[1].fX;
3167 if (!between(x0, x, x1)) {
3168 return;
3169 }
3170 SkScalar dx = x1 - x0;
3171 SkScalar dy = y1 - y0;
3172 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3173 return;
3174 }
3175 SkVector v;
3176 v.set(dx, dy);
3177 tangents->push(v);
3178}
3179
reed@google.com4db592c2013-10-30 17:39:43 +00003180static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3181 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3182}
3183
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003184bool SkPath::contains(SkScalar x, SkScalar y) const {
3185 bool isInverse = this->isInverseFillType();
3186 if (this->isEmpty()) {
3187 return isInverse;
3188 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003189
reed@google.com4db592c2013-10-30 17:39:43 +00003190 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003191 return isInverse;
3192 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003193
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003194 SkPath::Iter iter(*this, true);
3195 bool done = false;
3196 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003197 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003198 do {
3199 SkPoint pts[4];
3200 switch (iter.next(pts, false)) {
3201 case SkPath::kMove_Verb:
3202 case SkPath::kClose_Verb:
3203 break;
3204 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003205 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003206 break;
3207 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003208 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003209 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003210 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003211 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003212 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003213 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003214 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003215 break;
3216 case SkPath::kDone_Verb:
3217 done = true;
3218 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003219 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003220 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003221 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3222 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3223 if (evenOddFill) {
3224 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003225 }
caryclark9aacd902015-12-14 08:38:09 -08003226 if (w) {
3227 return !isInverse;
3228 }
3229 if (onCurveCount <= 1) {
3230 return SkToBool(onCurveCount) ^ isInverse;
3231 }
3232 if ((onCurveCount & 1) || evenOddFill) {
3233 return SkToBool(onCurveCount & 1) ^ isInverse;
3234 }
halcanary9d524f22016-03-29 09:03:52 -07003235 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003236 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3237 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003238 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003239 SkTDArray<SkVector> tangents;
3240 do {
3241 SkPoint pts[4];
3242 int oldCount = tangents.count();
3243 switch (iter.next(pts, false)) {
3244 case SkPath::kMove_Verb:
3245 case SkPath::kClose_Verb:
3246 break;
3247 case SkPath::kLine_Verb:
3248 tangent_line(pts, x, y, &tangents);
3249 break;
3250 case SkPath::kQuad_Verb:
3251 tangent_quad(pts, x, y, &tangents);
3252 break;
3253 case SkPath::kConic_Verb:
3254 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3255 break;
3256 case SkPath::kCubic_Verb:
3257 tangent_cubic(pts, x, y, &tangents);
3258 break;
3259 case SkPath::kDone_Verb:
3260 done = true;
3261 break;
3262 }
3263 if (tangents.count() > oldCount) {
3264 int last = tangents.count() - 1;
3265 const SkVector& tangent = tangents[last];
3266 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3267 tangents.remove(last);
3268 } else {
3269 for (int index = 0; index < last; ++index) {
3270 const SkVector& test = tangents[index];
3271 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003272 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3273 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003274 tangents.remove(last);
3275 tangents.removeShuffle(index);
3276 break;
3277 }
3278 }
3279 }
3280 }
3281 } while (!done);
3282 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003283}
fmalitaaa0df4e2015-12-01 09:13:23 -08003284
3285int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3286 SkScalar w, SkPoint pts[], int pow2) {
3287 const SkConic conic(p0, p1, p2, w);
3288 return conic.chopIntoQuadsPOW2(pts, pow2);
3289}
bsalomonedc743a2016-06-01 09:42:31 -07003290
3291bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3292 unsigned* start) {
3293 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3294 return false;
3295 }
3296 SkPath::RawIter iter(path);
3297 SkPoint verbPts[4];
3298 SkPath::Verb v;
3299 SkPoint rectPts[5];
3300 int rectPtCnt = 0;
3301 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3302 switch (v) {
3303 case SkPath::kMove_Verb:
3304 if (0 != rectPtCnt) {
3305 return false;
3306 }
3307 rectPts[0] = verbPts[0];
3308 ++rectPtCnt;
3309 break;
3310 case SkPath::kLine_Verb:
3311 if (5 == rectPtCnt) {
3312 return false;
3313 }
3314 rectPts[rectPtCnt] = verbPts[1];
3315 ++rectPtCnt;
3316 break;
3317 case SkPath::kClose_Verb:
3318 if (4 == rectPtCnt) {
3319 rectPts[4] = rectPts[0];
3320 rectPtCnt = 5;
3321 }
3322 break;
3323 default:
3324 return false;
3325 }
3326 }
3327 if (rectPtCnt < 5) {
3328 return false;
3329 }
3330 if (rectPts[0] != rectPts[4]) {
3331 return false;
3332 }
bsalomon057ae8a2016-07-24 05:37:26 -07003333 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3334 // and pts 1 and 2 the opposite vertical or horizontal edge).
3335 bool vec03IsVertical;
3336 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3337 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3338 // Make sure it has non-zero width and height
3339 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003340 return false;
3341 }
bsalomon057ae8a2016-07-24 05:37:26 -07003342 vec03IsVertical = true;
3343 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3344 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3345 // Make sure it has non-zero width and height
3346 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3347 return false;
3348 }
3349 vec03IsVertical = false;
3350 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003351 return false;
3352 }
bsalomon057ae8a2016-07-24 05:37:26 -07003353 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3354 // set if it is on the bottom edge.
3355 unsigned sortFlags =
3356 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3357 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3358 switch (sortFlags) {
3359 case 0b00:
3360 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3361 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3362 *start = 0;
3363 break;
3364 case 0b01:
3365 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3366 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3367 *start = 1;
3368 break;
3369 case 0b10:
3370 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3371 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3372 *start = 3;
3373 break;
3374 case 0b11:
3375 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3376 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3377 *start = 2;
3378 break;
bsalomonedc743a2016-06-01 09:42:31 -07003379 }
bsalomonedc743a2016-06-01 09:42:31 -07003380 return true;
3381}
bsalomon21af9ca2016-08-25 12:29:23 -07003382
3383void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3384 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3385 SkASSERT(!oval.isEmpty());
3386 SkASSERT(sweepAngle);
3387
3388 path->reset();
3389 path->setIsVolatile(true);
3390 path->setFillType(SkPath::kWinding_FillType);
3391 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3392 path->addOval(oval);
3393 return;
3394 }
3395 if (useCenter) {
3396 path->moveTo(oval.centerX(), oval.centerY());
3397 }
3398 // Arc to mods at 360 and drawArc is not supposed to.
3399 bool forceMoveTo = !useCenter;
3400 while (sweepAngle <= -360.f) {
3401 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3402 startAngle -= 180.f;
3403 path->arcTo(oval, startAngle, -180.f, false);
3404 startAngle -= 180.f;
3405 forceMoveTo = false;
3406 sweepAngle += 360.f;
3407 }
3408 while (sweepAngle >= 360.f) {
3409 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3410 startAngle += 180.f;
3411 path->arcTo(oval, startAngle, 180.f, false);
3412 startAngle += 180.f;
3413 forceMoveTo = false;
3414 sweepAngle -= 360.f;
3415 }
3416 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3417 if (useCenter) {
3418 path->close();
3419 }
3420}
Mike Reed0d7dac82017-02-02 17:45:56 -08003421
3422///////////////////////////////////////////////////////////////////////////////////////////////////
3423#include "SkNx.h"
3424
3425static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3426 SkScalar ts[2];
3427 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3428 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3429 SkASSERT(n >= 0 && n <= 2);
3430 for (int i = 0; i < n; ++i) {
3431 extremas[i] = SkEvalQuadAt(src, ts[i]);
3432 }
3433 extremas[n] = src[2];
3434 return n + 1;
3435}
3436
3437static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3438 SkConic conic(src[0], src[1], src[2], w);
3439 SkScalar ts[2];
3440 int n = conic.findXExtrema(ts);
3441 n += conic.findYExtrema(&ts[n]);
3442 SkASSERT(n >= 0 && n <= 2);
3443 for (int i = 0; i < n; ++i) {
3444 extremas[i] = conic.evalAt(ts[i]);
3445 }
3446 extremas[n] = src[2];
3447 return n + 1;
3448}
3449
3450static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3451 SkScalar ts[4];
3452 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3453 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3454 SkASSERT(n >= 0 && n <= 4);
3455 for (int i = 0; i < n; ++i) {
3456 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3457 }
3458 extremas[n] = src[3];
3459 return n + 1;
3460}
3461
Mike Reed8d3196b2017-02-03 11:34:13 -05003462SkRect SkPath::computeTightBounds() const {
3463 if (0 == this->countVerbs()) {
3464 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003465 }
3466
Mike Reed8d3196b2017-02-03 11:34:13 -05003467 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3468 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003469 }
3470
3471 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3472 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003473 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003474
3475 // initial with the first MoveTo, so we don't have to check inside the switch
3476 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003477 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003478 for (;;) {
3479 int count = 0;
3480 switch (iter.next(pts)) {
3481 case SkPath::kMove_Verb:
3482 extremas[0] = pts[0];
3483 count = 1;
3484 break;
3485 case SkPath::kLine_Verb:
3486 extremas[0] = pts[1];
3487 count = 1;
3488 break;
3489 case SkPath::kQuad_Verb:
3490 count = compute_quad_extremas(pts, extremas);
3491 break;
3492 case SkPath::kConic_Verb:
3493 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3494 break;
3495 case SkPath::kCubic_Verb:
3496 count = compute_cubic_extremas(pts, extremas);
3497 break;
3498 case SkPath::kClose_Verb:
3499 break;
3500 case SkPath::kDone_Verb:
3501 goto DONE;
3502 }
3503 for (int i = 0; i < count; ++i) {
3504 Sk2s tmp = from_point(extremas[i]);
3505 min = Sk2s::Min(min, tmp);
3506 max = Sk2s::Max(max, tmp);
3507 }
3508 }
3509DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003510 SkRect bounds;
3511 min.store((SkPoint*)&bounds.fLeft);
3512 max.store((SkPoint*)&bounds.fRight);
3513 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003514}