blob: a4609904299562a40095d59486153cb1c310b64a [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;
jvanverthb3eb6872014-10-24 07:12:51 -0700178 fIsVolatile = that.fIsVolatile;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400179
180 // Non-atomic assignment of atomic values.
181 fConvexity .store(that.fConvexity .load());
182 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183}
184
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000185bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000186 // note: don't need to look at isConvex or bounds, since just comparing the
187 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000188 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000189 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000190}
191
bungeman@google.coma5809a32013-06-21 15:13:34 +0000192void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700194 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000195 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700197 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400198
199 // Non-atomic swaps of atomic values.
200 Convexity c = fConvexity.load();
201 fConvexity.store(that.fConvexity.load());
202 that.fConvexity.store(c);
203
204 uint8_t fd = fFirstDirection.load();
205 fFirstDirection.store(that.fFirstDirection.load());
206 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 }
208}
209
caryclark8e7b19d2016-02-18 04:11:48 -0800210bool SkPath::isInterpolatable(const SkPath& compare) const {
211 int count = fPathRef->countVerbs();
212 if (count != compare.fPathRef->countVerbs()) {
213 return false;
214 }
215 if (!count) {
216 return true;
217 }
218 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
219 count)) {
220 return false;
221 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800222 return !fPathRef->countWeights() ||
223 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800224 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
225}
226
227bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
228 int verbCount = fPathRef->countVerbs();
229 if (verbCount != ending.fPathRef->countVerbs()) {
230 return false;
231 }
caryclarka1105382016-02-18 06:13:25 -0800232 if (!verbCount) {
233 return true;
234 }
caryclark8e7b19d2016-02-18 04:11:48 -0800235 out->reset();
236 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700237 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800238 return true;
239}
240
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000241static inline bool check_edge_against_rect(const SkPoint& p0,
242 const SkPoint& p1,
243 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700244 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000245 const SkPoint* edgeBegin;
246 SkVector v;
reed026beb52015-06-10 14:23:15 -0700247 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000248 v = p1 - p0;
249 edgeBegin = &p0;
250 } else {
251 v = p0 - p1;
252 edgeBegin = &p1;
253 }
254 if (v.fX || v.fY) {
255 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500256 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
257 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
258 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
259 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000260 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
261 return false;
262 }
263 }
264 return true;
265}
266
267bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
268 // This only handles non-degenerate convex paths currently.
269 if (kConvex_Convexity != this->getConvexity()) {
270 return false;
271 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000272
reed026beb52015-06-10 14:23:15 -0700273 SkPathPriv::FirstDirection direction;
274 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 return false;
276 }
277
278 SkPoint firstPt;
279 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500280 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000281 SkPath::Verb verb;
282 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400283 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000284 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000285 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286
Lee Salzmana19f0242017-01-12 13:06:21 -0500287 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 int nextPt = -1;
289 switch (verb) {
290 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000291 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000292 SkDEBUGCODE(++moveCnt);
293 firstPt = prevPt = pts[0];
294 break;
295 case kLine_Verb:
296 nextPt = 1;
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 break;
300 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000301 case kConic_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 = 2;
305 break;
306 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 3;
310 break;
311 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000313 break;
314 default:
315 SkDEBUGFAIL("unknown verb");
316 }
317 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800318 if (SkPath::kConic_Verb == verb) {
319 SkConic orig;
320 orig.set(pts, iter.conicWeight());
321 SkPoint quadPts[5];
322 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800323 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800324
325 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
326 return false;
327 }
328 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
329 return false;
330 }
331 } else {
332 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
333 return false;
334 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000335 }
336 prevPt = pts[nextPt];
337 }
338 }
339
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400340 if (segmentCount) {
341 return check_edge_against_rect(prevPt, firstPt, rect, direction);
342 }
343 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000344}
345
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000346uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000347 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800348#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400349 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
350 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000351#endif
352 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000353}
djsollen@google.come63793a2012-03-21 15:39:03 +0000354
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355void SkPath::reset() {
356 SkDEBUGCODE(this->validate();)
357
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000358 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000359 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360}
361
362void SkPath::rewind() {
363 SkDEBUGCODE(this->validate();)
364
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000365 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000366 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367}
368
fsb1475b02016-01-20 09:51:07 -0800369bool SkPath::isLastContourClosed() const {
370 int verbCount = fPathRef->countVerbs();
371 if (0 == verbCount) {
372 return false;
373 }
374 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
375}
376
reed@google.com7e6c4d12012-05-10 14:05:43 +0000377bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000378 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000379
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000380 if (2 == verbCount) {
381 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
382 if (kLine_Verb == fPathRef->atVerb(1)) {
383 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000384 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000385 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000386 line[0] = pts[0];
387 line[1] = pts[1];
388 }
389 return true;
390 }
391 }
392 return false;
393}
394
caryclark@google.comf1316942011-07-26 19:54:45 +0000395/*
396 Determines if path is a rect by keeping track of changes in direction
397 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000398
caryclark@google.comf1316942011-07-26 19:54:45 +0000399 The direction is computed such that:
400 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000401 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000402 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000403 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405A rectangle cycles up/right/down/left or up/left/down/right.
406
407The test fails if:
408 The path is closed, and followed by a line.
409 A second move creates a new endpoint.
410 A diagonal line is parsed.
411 There's more than four changes of direction.
412 There's a discontinuity on the line (e.g., a move in the middle)
413 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 The path contains a quadratic or cubic.
415 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000416 *The rectangle doesn't complete a cycle.
417 *The final point isn't equal to the first point.
418
419 *These last two conditions we relax if we have a 3-edge path that would
420 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000421
caryclark@google.comf1316942011-07-26 19:54:45 +0000422It's OK if the path has:
423 Several colinear line segments composing a rectangle side.
424 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000425
caryclark@google.comf1316942011-07-26 19:54:45 +0000426The direction takes advantage of the corners found since opposite sides
427must travel in opposite directions.
428
429FIXME: Allow colinear quads and cubics to be treated like lines.
430FIXME: If the API passes fill-only, return true if the filled stroke
431 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000432
433 first,last,next direction state-machine:
434 0x1 is set if the segment is horizontal
435 0x2 is set if the segment is moving to the right or down
436 thus:
437 two directions are opposites iff (dirA ^ dirB) == 0x2
438 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000439
caryclark@google.comf1316942011-07-26 19:54:45 +0000440 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000441static int rect_make_dir(SkScalar dx, SkScalar dy) {
442 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
443}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000444bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
445 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 int corners = 0;
447 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000448 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700449 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000450 first.set(0, 0);
451 last.set(0, 0);
452 int firstDirection = 0;
453 int lastDirection = 0;
454 int nextDirection = 0;
455 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000456 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700457 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000458 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000459 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700460 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
461 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000463 savePts = pts;
464 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000465 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700466 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000467 case kLine_Verb: {
468 SkScalar left = last.fX;
469 SkScalar top = last.fY;
470 SkScalar right = pts->fX;
471 SkScalar bottom = pts->fY;
472 ++pts;
473 if (left != right && top != bottom) {
474 return false; // diagonal
475 }
476 if (left == right && top == bottom) {
477 break; // single point on side OK
478 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000479 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000480 if (0 == corners) {
481 firstDirection = nextDirection;
482 first = last;
483 last = pts[-1];
484 corners = 1;
485 closedOrMoved = false;
486 break;
487 }
488 if (closedOrMoved) {
489 return false; // closed followed by a line
490 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000491 if (autoClose && nextDirection == firstDirection) {
492 break; // colinear with first
493 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 closedOrMoved = autoClose;
495 if (lastDirection != nextDirection) {
496 if (++corners > 4) {
497 return false; // too many direction changes
498 }
499 }
500 last = pts[-1];
501 if (lastDirection == nextDirection) {
502 break; // colinear segment
503 }
504 // Possible values for corners are 2, 3, and 4.
505 // When corners == 3, nextDirection opposes firstDirection.
506 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000507 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
509 if ((directionCycle ^ turn) != nextDirection) {
510 return false; // direction didn't follow cycle
511 }
512 break;
513 }
514 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000515 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 case kCubic_Verb:
517 return false; // quadratic, cubic not allowed
518 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700519 if (allowPartial && !autoClose && firstDirection) {
520 insertClose = true;
521 *currVerb -= 1; // try move again afterwards
522 goto addMissingClose;
523 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000524 last = *pts++;
525 closedOrMoved = true;
526 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000527 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000528 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000529 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000530 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000531 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000532 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700533addMissingClose:
534 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000535 }
536 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000537 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000538 if (!result) {
539 // check if we are just an incomplete rectangle, in which case we can
540 // return true, but not claim to be closed.
541 // e.g.
542 // 3 sided rectangle
543 // 4 sided but the last edge is not long enough to reach the start
544 //
545 SkScalar closeX = first.x() - last.x();
546 SkScalar closeY = first.y() - last.y();
547 if (closeX && closeY) {
548 return false; // we're diagonal, abort (can we ever reach this?)
549 }
550 int closeDirection = rect_make_dir(closeX, closeY);
551 // make sure the close-segment doesn't double-back on itself
552 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
553 result = true;
554 autoClose = false; // we are not closed
555 }
556 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000557 if (savePts) {
558 *ptsPtr = savePts;
559 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000560 if (result && isClosed) {
561 *isClosed = autoClose;
562 }
563 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000564 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000565 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000566 return result;
567}
568
robertphillips4f662e62014-12-29 14:06:51 -0800569bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000570 SkDEBUGCODE(this->validate();)
571 int currVerb = 0;
572 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800573 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800574 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800575 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000576 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800577 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800578 int32_t num = SkToS32(pts - first);
579 if (num) {
580 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800581 } else {
582 // 'pts' isn't updated for open rects
583 *rect = this->getBounds();
584 }
585 }
586 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000587}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000588
caryclark95bc5f32015-04-08 08:34:15 -0700589bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000590 SkDEBUGCODE(this->validate();)
591 int currVerb = 0;
592 const SkPoint* pts = fPathRef->points();
593 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000594 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700595 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000596 return false;
597 }
598 const SkPoint* last = pts;
599 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700600 bool isClosed;
601 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000602 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700603 if (!isClosed) {
604 pts = fPathRef->points() + fPathRef->countPoints();
605 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000606 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000607 if (testRects[0].contains(testRects[1])) {
608 if (rects) {
609 rects[0] = testRects[0];
610 rects[1] = testRects[1];
611 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000612 if (dirs) {
613 dirs[0] = testDirs[0];
614 dirs[1] = testDirs[1];
615 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000616 return true;
617 }
618 if (testRects[1].contains(testRects[0])) {
619 if (rects) {
620 rects[0] = testRects[1];
621 rects[1] = testRects[0];
622 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000623 if (dirs) {
624 dirs[0] = testDirs[1];
625 dirs[1] = testDirs[0];
626 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000627 return true;
628 }
629 }
630 return false;
631}
632
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633int SkPath::countPoints() const {
634 return fPathRef->countPoints();
635}
636
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000637int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkDEBUGCODE(this->validate();)
639
640 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000641 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800643 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645}
646
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000647SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000648 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
649 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000650 }
651 return SkPoint::Make(0, 0);
652}
653
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654int SkPath::countVerbs() const {
655 return fPathRef->countVerbs();
656}
657
658static inline void copy_verbs_reverse(uint8_t* inorderDst,
659 const uint8_t* reversedSrc,
660 int count) {
661 for (int i = 0; i < count; ++i) {
662 inorderDst[i] = reversedSrc[~i];
663 }
664}
665
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000666int SkPath::getVerbs(uint8_t dst[], int max) const {
667 SkDEBUGCODE(this->validate();)
668
669 SkASSERT(max >= 0);
670 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 int count = SkMin32(max, fPathRef->countVerbs());
672 copy_verbs_reverse(dst, fPathRef->verbs(), count);
673 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000674}
675
reed@google.com294dd7b2011-10-11 11:58:32 +0000676bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000680 if (count > 0) {
681 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000684 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000686 if (lastPt) {
687 lastPt->set(0, 0);
688 }
689 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690}
691
caryclarkaec25102015-04-29 08:28:30 -0700692void SkPath::setPt(int index, SkScalar x, SkScalar y) {
693 SkDEBUGCODE(this->validate();)
694
695 int count = fPathRef->countPoints();
696 if (count <= index) {
697 return;
698 } else {
699 SkPathRef::Editor ed(&fPathRef);
700 ed.atPoint(index)->set(x, y);
701 }
702}
703
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704void SkPath::setLastPt(SkScalar x, SkScalar y) {
705 SkDEBUGCODE(this->validate();)
706
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 if (count == 0) {
709 this->moveTo(x, y);
710 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000711 SkPathRef::Editor ed(&fPathRef);
712 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 }
714}
715
reed@google.com04863fa2011-05-15 04:08:24 +0000716void SkPath::setConvexity(Convexity c) {
717 if (fConvexity != c) {
718 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000719 }
720}
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722//////////////////////////////////////////////////////////////////////////////
723// Construction methods
724
reed026beb52015-06-10 14:23:15 -0700725#define DIRTY_AFTER_EDIT \
726 do { \
727 fConvexity = kUnknown_Convexity; \
728 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000729 } while (0)
730
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731void SkPath::incReserve(U16CPU inc) {
732 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000733 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000734 SkDEBUGCODE(this->validate();)
735}
736
737void SkPath::moveTo(SkScalar x, SkScalar y) {
738 SkDEBUGCODE(this->validate();)
739
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000742 // remember our index
743 fLastMoveToIndex = fPathRef->countPoints();
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700746
747 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000748}
749
750void SkPath::rMoveTo(SkScalar x, SkScalar y) {
751 SkPoint pt;
752 this->getLastPt(&pt);
753 this->moveTo(pt.fX + x, pt.fY + y);
754}
755
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000756void SkPath::injectMoveToIfNeeded() {
757 if (fLastMoveToIndex < 0) {
758 SkScalar x, y;
759 if (fPathRef->countVerbs() == 0) {
760 x = y = 0;
761 } else {
762 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
763 x = pt.fX;
764 y = pt.fY;
765 }
766 this->moveTo(x, y);
767 }
768}
769
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770void SkPath::lineTo(SkScalar x, SkScalar y) {
771 SkDEBUGCODE(this->validate();)
772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 this->injectMoveToIfNeeded();
774
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000775 SkPathRef::Editor ed(&fPathRef);
776 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777
reed@google.comb54455e2011-05-16 14:16:04 +0000778 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779}
780
781void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000782 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 SkPoint pt;
784 this->getLastPt(&pt);
785 this->lineTo(pt.fX + x, pt.fY + y);
786}
787
788void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
789 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000790
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000791 this->injectMoveToIfNeeded();
792
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000793 SkPathRef::Editor ed(&fPathRef);
794 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795 pts[0].set(x1, y1);
796 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000797
reed@google.comb54455e2011-05-16 14:16:04 +0000798 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799}
800
801void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000802 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 SkPoint pt;
804 this->getLastPt(&pt);
805 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
806}
807
reed@google.com277c3f82013-05-31 15:17:50 +0000808void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
809 SkScalar w) {
810 // check for <= 0 or NaN with this test
811 if (!(w > 0)) {
812 this->lineTo(x2, y2);
813 } else if (!SkScalarIsFinite(w)) {
814 this->lineTo(x1, y1);
815 this->lineTo(x2, y2);
816 } else if (SK_Scalar1 == w) {
817 this->quadTo(x1, y1, x2, y2);
818 } else {
819 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000820
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000821 this->injectMoveToIfNeeded();
822
reed@google.com277c3f82013-05-31 15:17:50 +0000823 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000824 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000825 pts[0].set(x1, y1);
826 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000827
reed@google.com277c3f82013-05-31 15:17:50 +0000828 DIRTY_AFTER_EDIT;
829 }
830}
831
832void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
833 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000834 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000835 SkPoint pt;
836 this->getLastPt(&pt);
837 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
838}
839
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
841 SkScalar x3, SkScalar y3) {
842 SkDEBUGCODE(this->validate();)
843
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000844 this->injectMoveToIfNeeded();
845
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 SkPathRef::Editor ed(&fPathRef);
847 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 pts[0].set(x1, y1);
849 pts[1].set(x2, y2);
850 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851
reed@google.comb54455e2011-05-16 14:16:04 +0000852 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853}
854
855void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
856 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000857 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 SkPoint pt;
859 this->getLastPt(&pt);
860 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
861 pt.fX + x3, pt.fY + y3);
862}
863
864void SkPath::close() {
865 SkDEBUGCODE(this->validate();)
866
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000867 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 case kLine_Verb:
871 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000872 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 case kMove_Verb: {
875 SkPathRef::Editor ed(&fPathRef);
876 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000878 }
reed@google.com277c3f82013-05-31 15:17:50 +0000879 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000880 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000881 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000882 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000883 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000884 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000885 }
886 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000887
888 // signal that we need a moveTo to follow us (unless we're done)
889#if 0
890 if (fLastMoveToIndex >= 0) {
891 fLastMoveToIndex = ~fLastMoveToIndex;
892 }
893#else
894 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
895#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896}
897
898///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000899
fmalitac08d53e2015-11-17 09:53:29 -0800900namespace {
901
902template <unsigned N>
903class PointIterator {
904public:
905 PointIterator(SkPath::Direction dir, unsigned startIndex)
906 : fCurrent(startIndex % N)
907 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
908
909 const SkPoint& current() const {
910 SkASSERT(fCurrent < N);
911 return fPts[fCurrent];
912 }
913
914 const SkPoint& next() {
915 fCurrent = (fCurrent + fAdvance) % N;
916 return this->current();
917 }
918
919protected:
920 SkPoint fPts[N];
921
922private:
923 unsigned fCurrent;
924 unsigned fAdvance;
925};
926
927class RectPointIterator : public PointIterator<4> {
928public:
929 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
930 : PointIterator(dir, startIndex) {
931
932 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
933 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
934 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
935 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
936 }
937};
938
939class OvalPointIterator : public PointIterator<4> {
940public:
941 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
942 : PointIterator(dir, startIndex) {
943
944 const SkScalar cx = oval.centerX();
945 const SkScalar cy = oval.centerY();
946
947 fPts[0] = SkPoint::Make(cx, oval.fTop);
948 fPts[1] = SkPoint::Make(oval.fRight, cy);
949 fPts[2] = SkPoint::Make(cx, oval.fBottom);
950 fPts[3] = SkPoint::Make(oval.fLeft, cy);
951 }
952};
953
954class RRectPointIterator : public PointIterator<8> {
955public:
956 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
957 : PointIterator(dir, startIndex) {
958
959 const SkRect& bounds = rrect.getBounds();
960 const SkScalar L = bounds.fLeft;
961 const SkScalar T = bounds.fTop;
962 const SkScalar R = bounds.fRight;
963 const SkScalar B = bounds.fBottom;
964
965 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
966 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
967 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
968 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
969 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
970 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
971 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
972 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
973 }
974};
975
976} // anonymous namespace
977
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000978static void assert_known_direction(int dir) {
979 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
980}
981
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800983 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984}
985
986void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
987 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800988 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
989}
990
991void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000992 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700993 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800994 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000995 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800996 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000997
fmalitac08d53e2015-11-17 09:53:29 -0800998 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000999
fmalitac08d53e2015-11-17 09:53:29 -08001000 const int kVerbs = 5; // moveTo + 3x lineTo + close
1001 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001002
fmalitac08d53e2015-11-17 09:53:29 -08001003 RectPointIterator iter(rect, dir, startIndex);
1004
1005 this->moveTo(iter.current());
1006 this->lineTo(iter.next());
1007 this->lineTo(iter.next());
1008 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001010
1011 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012}
1013
reed@google.com744faba2012-05-29 19:54:52 +00001014void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1015 SkDEBUGCODE(this->validate();)
1016 if (count <= 0) {
1017 return;
1018 }
1019
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001020 fLastMoveToIndex = fPathRef->countPoints();
1021
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001022 // +close makes room for the extra kClose_Verb
1023 SkPathRef::Editor ed(&fPathRef, count+close, count);
1024
1025 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001026 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001027 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1028 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001029 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001030
reed@google.com744faba2012-05-29 19:54:52 +00001031 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001033 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001034 }
1035
reed@google.com744faba2012-05-29 19:54:52 +00001036 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001037 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001038}
1039
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001040#include "SkGeometry.h"
1041
reedf90ea012015-01-29 12:03:58 -08001042static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1043 SkPoint* pt) {
1044 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045 // Chrome uses this path to move into and out of ovals. If not
1046 // treated as a special case the moves can distort the oval's
1047 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001048 pt->set(oval.fRight, oval.centerY());
1049 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050 } else if (0 == oval.width() && 0 == oval.height()) {
1051 // Chrome will sometimes create 0 radius round rects. Having degenerate
1052 // quad segments in the path prevents the path from being recognized as
1053 // a rect.
1054 // TODO: optimizing the case where only one of width or height is zero
1055 // should also be considered. This case, however, doesn't seem to be
1056 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001057 pt->set(oval.fRight, oval.fTop);
1058 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001059 }
reedf90ea012015-01-29 12:03:58 -08001060 return false;
1061}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001062
reedd5d27d92015-02-09 13:54:43 -08001063// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1064//
1065static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1066 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1067 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1068 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001069
1070 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001071 loss in radians-conversion and/or sin/cos, we may end up with coincident
1072 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1073 of drawing a nearly complete circle (good).
1074 e.g. canvas.drawArc(0, 359.99, ...)
1075 -vs- canvas.drawArc(0, 359.9, ...)
1076 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001077 */
reedd5d27d92015-02-09 13:54:43 -08001078 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079 SkScalar sw = SkScalarAbs(sweepAngle);
1080 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1081 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1082 // make a guess at a tiny angle (in radians) to tweak by
1083 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1084 // not sure how much will be enough, so we use a loop
1085 do {
1086 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001087 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1088 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 }
1090 }
reedd5d27d92015-02-09 13:54:43 -08001091 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1092}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001093
reed9e779d42015-02-17 11:43:14 -08001094/**
1095 * If this returns 0, then the caller should just line-to the singlePt, else it should
1096 * ignore singlePt and append the specified number of conics.
1097 */
reedd5d27d92015-02-09 13:54:43 -08001098static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001099 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1100 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001101 SkMatrix matrix;
1102
1103 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1104 matrix.postTranslate(oval.centerX(), oval.centerY());
1105
reed9e779d42015-02-17 11:43:14 -08001106 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1107 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001108 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001109 }
1110 return count;
reedd5d27d92015-02-09 13:54:43 -08001111}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001112
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001113void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115 SkRRect rrect;
1116 rrect.setRectRadii(rect, (const SkVector*) radii);
1117 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001118}
1119
reed@google.com4ed0fb72012-12-12 20:48:18 +00001120void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001121 // legacy start indices: 6 (CW) and 7(CCW)
1122 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1123}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001124
fmalitac08d53e2015-11-17 09:53:29 -08001125void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1126 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001127
fmalitac08d53e2015-11-17 09:53:29 -08001128 if (rrect.isEmpty()) {
1129 return;
reed1b28a3a2014-12-17 14:42:09 -08001130 }
fmalitac08d53e2015-11-17 09:53:29 -08001131
caryclarkda707bf2015-11-19 14:47:43 -08001132 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001133 const SkRect& bounds = rrect.getBounds();
1134
1135 if (rrect.isRect()) {
1136 // degenerate(rect) => radii points are collapsing
1137 this->addRect(bounds, dir, (startIndex + 1) / 2);
1138 } else if (rrect.isOval()) {
1139 // degenerate(oval) => line points are collapsing
1140 this->addOval(bounds, dir, startIndex / 2);
1141 } else {
1142 fFirstDirection = this->hasOnlyMoveTos() ?
1143 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1144
1145 SkAutoPathBoundsUpdate apbu(this, bounds);
1146 SkAutoDisableDirectionCheck addc(this);
1147
1148 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1149 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1150 const SkScalar weight = SK_ScalarRoot2Over2;
1151
1152 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1153 const int kVerbs = startsWithConic
1154 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1155 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1156 this->incReserve(kVerbs);
1157
1158 RRectPointIterator rrectIter(rrect, dir, startIndex);
1159 // Corner iterator indices follow the collapsed radii model,
1160 // adjusted such that the start pt is "behind" the radii start pt.
1161 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1162 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1163
1164 this->moveTo(rrectIter.current());
1165 if (startsWithConic) {
1166 for (unsigned i = 0; i < 3; ++i) {
1167 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1168 this->lineTo(rrectIter.next());
1169 }
1170 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1171 // final lineTo handled by close().
1172 } else {
1173 for (unsigned i = 0; i < 4; ++i) {
1174 this->lineTo(rrectIter.next());
1175 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1176 }
1177 }
1178 this->close();
1179
caryclarkda707bf2015-11-19 14:47:43 -08001180 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001181 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001182
fmalitac08d53e2015-11-17 09:53:29 -08001183 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1184 }
1185
1186 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001187}
1188
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001189bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001190 int count = fPathRef->countVerbs();
1191 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1192 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001193 if (*verbs == kLine_Verb ||
1194 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001195 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001196 *verbs == kCubic_Verb) {
1197 return false;
1198 }
1199 ++verbs;
1200 }
1201 return true;
1202}
1203
Brian Osmana2318572017-07-10 15:09:26 -04001204bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1205 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001206 if (count < 2) {
1207 return true;
1208 }
Brian Osmana2318572017-07-10 15:09:26 -04001209 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001210 const SkPoint& first = *pts;
1211 for (int index = 1; index < count; ++index) {
1212 if (first != pts[index]) {
1213 return false;
1214 }
1215 }
1216 return true;
1217}
1218
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001219void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1220 Direction dir) {
1221 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001222
humper@google.com75e3ca12013-04-08 21:44:11 +00001223 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001224 return;
1225 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001226
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001227 SkRRect rrect;
1228 rrect.setRectXY(rect, rx, ry);
1229 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001230}
1231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001233 // legacy start index: 1
1234 this->addOval(oval, dir, 1);
1235}
1236
1237void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001238 assert_known_direction(dir);
1239
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001240 /* If addOval() is called after previous moveTo(),
1241 this path is still marked as an oval. This is used to
1242 fit into WebKit's calling sequences.
1243 We can't simply check isEmpty() in this case, as additional
1244 moveTo() would mark the path non empty.
1245 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001246 bool isOval = hasOnlyMoveTos();
1247 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001248 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001249 } else {
reed026beb52015-06-10 14:23:15 -07001250 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001251 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001252
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001253 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001254 SkAutoPathBoundsUpdate apbu(this, oval);
1255
fmalitac08d53e2015-11-17 09:53:29 -08001256 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1257 const int kVerbs = 6; // moveTo + 4x conicTo + close
1258 this->incReserve(kVerbs);
1259
1260 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1261 // The corner iterator pts are tracking "behind" the oval/radii pts.
1262 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001263 const SkScalar weight = SK_ScalarRoot2Over2;
1264
fmalitac08d53e2015-11-17 09:53:29 -08001265 this->moveTo(ovalIter.current());
1266 for (unsigned i = 0; i < 4; ++i) {
1267 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001268 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270
fmalitac08d53e2015-11-17 09:53:29 -08001271 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1272
robertphillips@google.com466310d2013-12-03 16:43:54 +00001273 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001274
bsalomon78d58d12016-05-27 09:17:04 -07001275 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001276}
1277
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1279 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001280 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 }
1282}
1283
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1285 bool forceMoveTo) {
1286 if (oval.width() < 0 || oval.height() < 0) {
1287 return;
1288 }
1289
reedf90ea012015-01-29 12:03:58 -08001290 if (fPathRef->countVerbs() == 0) {
1291 forceMoveTo = true;
1292 }
1293
1294 SkPoint lonePt;
1295 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1296 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1297 return;
1298 }
1299
reedd5d27d92015-02-09 13:54:43 -08001300 SkVector startV, stopV;
1301 SkRotationDirection dir;
1302 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1303
reed9e779d42015-02-17 11:43:14 -08001304 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001305
1306 // At this point, we know that the arc is not a lone point, but startV == stopV
1307 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1308 // cannot handle it.
1309 if (startV == stopV) {
1310 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1311 SkScalar radiusX = oval.width() / 2;
1312 SkScalar radiusY = oval.height() / 2;
1313 // We cannot use SkScalarSinCos function in the next line because
1314 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1315 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001316 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001317 // make sin(endAngle) to be 0 which will then draw a dot.
1318 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1319 oval.centerY() + radiusY * sk_float_sin(endAngle));
1320 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1321 return;
1322 }
1323
reedd5d27d92015-02-09 13:54:43 -08001324 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001325 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001326 if (count) {
1327 this->incReserve(count * 2 + 1);
1328 const SkPoint& pt = conics[0].fPts[0];
1329 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1330 for (int i = 0; i < count; ++i) {
1331 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1332 }
reed9e779d42015-02-17 11:43:14 -08001333 } else {
1334 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001335 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336}
1337
caryclark55d49052016-01-23 05:07:04 -08001338// This converts the SVG arc to conics.
1339// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1340// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1341// See also SVG implementation notes:
1342// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1343// Note that arcSweep bool value is flipped from the original implementation.
1344void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1345 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001346 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001347 SkPoint srcPts[2];
1348 this->getLastPt(&srcPts[0]);
1349 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1350 // joining the endpoints.
1351 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1352 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001353 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001354 return;
1355 }
1356 // If the current point and target point for the arc are identical, it should be treated as a
1357 // zero length path. This ensures continuity in animations.
1358 srcPts[1].set(x, y);
1359 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001360 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001361 return;
1362 }
1363 rx = SkScalarAbs(rx);
1364 ry = SkScalarAbs(ry);
1365 SkVector midPointDistance = srcPts[0] - srcPts[1];
1366 midPointDistance *= 0.5f;
1367
1368 SkMatrix pointTransform;
1369 pointTransform.setRotate(-angle);
1370
1371 SkPoint transformedMidPoint;
1372 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1373 SkScalar squareRx = rx * rx;
1374 SkScalar squareRy = ry * ry;
1375 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1376 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1377
1378 // Check if the radii are big enough to draw the arc, scale radii if not.
1379 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1380 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1381 if (radiiScale > 1) {
1382 radiiScale = SkScalarSqrt(radiiScale);
1383 rx *= radiiScale;
1384 ry *= radiiScale;
1385 }
1386
1387 pointTransform.setScale(1 / rx, 1 / ry);
1388 pointTransform.preRotate(-angle);
1389
1390 SkPoint unitPts[2];
1391 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1392 SkVector delta = unitPts[1] - unitPts[0];
1393
1394 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1395 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1396
1397 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1398 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1399 scaleFactor = -scaleFactor;
1400 }
1401 delta.scale(scaleFactor);
1402 SkPoint centerPoint = unitPts[0] + unitPts[1];
1403 centerPoint *= 0.5f;
1404 centerPoint.offset(-delta.fY, delta.fX);
1405 unitPts[0] -= centerPoint;
1406 unitPts[1] -= centerPoint;
1407 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1408 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1409 SkScalar thetaArc = theta2 - theta1;
1410 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1411 thetaArc += SK_ScalarPI * 2;
1412 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1413 thetaArc -= SK_ScalarPI * 2;
1414 }
1415 pointTransform.setRotate(angle);
1416 pointTransform.preScale(rx, ry);
1417
1418 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
1419 SkScalar thetaWidth = thetaArc / segments;
1420 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1421 if (!SkScalarIsFinite(t)) {
1422 return;
1423 }
1424 SkScalar startTheta = theta1;
1425 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
1426 for (int i = 0; i < segments; ++i) {
1427 SkScalar endTheta = startTheta + thetaWidth;
1428 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1429
1430 unitPts[1].set(cosEndTheta, sinEndTheta);
1431 unitPts[1] += centerPoint;
1432 unitPts[0] = unitPts[1];
1433 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1434 SkPoint mapped[2];
1435 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
1436 this->conicTo(mapped[0], mapped[1], w);
1437 startTheta = endTheta;
1438 }
1439}
1440
1441void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1442 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1443 SkPoint currentPoint;
1444 this->getLastPt(&currentPoint);
1445 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1446}
1447
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001448void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449 if (oval.isEmpty() || 0 == sweepAngle) {
1450 return;
1451 }
1452
1453 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1454
1455 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001456 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1457 // See SkPath::addOval() docs.
1458 SkScalar startOver90 = startAngle / 90.f;
1459 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1460 SkScalar error = startOver90 - startOver90I;
1461 if (SkScalarNearlyEqual(error, 0)) {
1462 // Index 1 is at startAngle == 0.
1463 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1464 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1465 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1466 (unsigned) startIndex);
1467 return;
1468 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 }
bsalomon1978ce22016-05-31 10:42:16 -07001470 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471}
1472
1473/*
1474 Need to handle the case when the angle is sharp, and our computed end-points
1475 for the arc go behind pt1 and/or p2...
1476*/
reedc7789042015-01-29 12:59:11 -08001477void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001478 if (radius == 0) {
1479 this->lineTo(x1, y1);
1480 return;
1481 }
1482
1483 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001484
reed@android.com8a1c16f2008-12-17 15:59:43 +00001485 // need to know our prev pt so we can construct tangent vectors
1486 {
1487 SkPoint start;
1488 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001489 // Handle degenerate cases by adding a line to the first point and
1490 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 before.setNormalize(x1 - start.fX, y1 - start.fY);
1492 after.setNormalize(x2 - x1, y2 - y1);
1493 }
reed@google.comabf15c12011-01-18 20:35:51 +00001494
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 SkScalar cosh = SkPoint::DotProduct(before, after);
1496 SkScalar sinh = SkPoint::CrossProduct(before, after);
1497
1498 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001499 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 return;
1501 }
reed@google.comabf15c12011-01-18 20:35:51 +00001502
Mike Reeda99b6ce2017-02-04 11:04:26 -05001503 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504
Mike Reeda99b6ce2017-02-04 11:04:26 -05001505 SkScalar xx = x1 - dist * before.fX;
1506 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001507 after.setLength(dist);
1508 this->lineTo(xx, yy);
1509 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1510 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511}
1512
1513///////////////////////////////////////////////////////////////////////////////
1514
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001515void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 SkMatrix matrix;
1517
1518 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001519 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520}
1521
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001522void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001525 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 SkPoint pts[4];
1527 Verb verb;
1528
1529 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001530 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 while ((verb = iter.next(pts)) != kDone_Verb) {
1532 switch (verb) {
1533 case kMove_Verb:
1534 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001535 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1536 injectMoveToIfNeeded(); // In case last contour is closed
1537 this->lineTo(pts[0]);
1538 } else {
1539 this->moveTo(pts[0]);
1540 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 break;
1542 case kLine_Verb:
1543 proc(matrix, &pts[1], &pts[1], 1);
1544 this->lineTo(pts[1]);
1545 break;
1546 case kQuad_Verb:
1547 proc(matrix, &pts[1], &pts[1], 2);
1548 this->quadTo(pts[1], pts[2]);
1549 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001550 case kConic_Verb:
1551 proc(matrix, &pts[1], &pts[1], 2);
1552 this->conicTo(pts[1], pts[2], iter.conicWeight());
1553 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 case kCubic_Verb:
1555 proc(matrix, &pts[1], &pts[1], 3);
1556 this->cubicTo(pts[1], pts[2], pts[3]);
1557 break;
1558 case kClose_Verb:
1559 this->close();
1560 break;
1561 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001562 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001564 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 }
1566}
1567
1568///////////////////////////////////////////////////////////////////////////////
1569
reed@google.com277c3f82013-05-31 15:17:50 +00001570static int pts_in_verb(unsigned verb) {
1571 static const uint8_t gPtsInVerb[] = {
1572 1, // kMove
1573 1, // kLine
1574 2, // kQuad
1575 2, // kConic
1576 3, // kCubic
1577 0, // kClose
1578 0 // kDone
1579 };
1580
1581 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1582 return gPtsInVerb[verb];
1583}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585// ignore the last point of the 1st contour
1586void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001587 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1588 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 return;
1590 }
caryclark51c56782016-11-07 05:09:28 -08001591 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1592 SkASSERT(verbsEnd[0] == kMove_Verb);
1593 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1594 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595
caryclark51c56782016-11-07 05:09:28 -08001596 while (verbs < verbsEnd) {
1597 uint8_t v = *verbs++;
1598 pts -= pts_in_verb(v);
1599 switch (v) {
1600 case kMove_Verb:
1601 // if the path has multiple contours, stop after reversing the last
1602 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001604 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 break;
1606 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001607 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001609 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001610 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001611 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001613 this->cubicTo(pts[2], pts[1], pts[0]);
1614 break;
1615 case kClose_Verb:
1616 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 break;
1618 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001619 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620 break;
1621 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001622 }
1623}
1624
reed@google.com63d73742012-01-10 15:33:12 +00001625void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001626 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001627
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001628 const SkPoint* pts = src.fPathRef->pointsEnd();
1629 // we will iterator through src's verbs backwards
1630 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1631 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001632 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001633
1634 bool needMove = true;
1635 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001636 while (verbs < verbsEnd) {
1637 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001638 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001639
1640 if (needMove) {
1641 --pts;
1642 this->moveTo(pts->fX, pts->fY);
1643 needMove = false;
1644 }
1645 pts -= n;
1646 switch (v) {
1647 case kMove_Verb:
1648 if (needClose) {
1649 this->close();
1650 needClose = false;
1651 }
1652 needMove = true;
1653 pts += 1; // so we see the point in "if (needMove)" above
1654 break;
1655 case kLine_Verb:
1656 this->lineTo(pts[0]);
1657 break;
1658 case kQuad_Verb:
1659 this->quadTo(pts[1], pts[0]);
1660 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001661 case kConic_Verb:
1662 this->conicTo(pts[1], pts[0], *--conicWeights);
1663 break;
reed@google.com63d73742012-01-10 15:33:12 +00001664 case kCubic_Verb:
1665 this->cubicTo(pts[2], pts[1], pts[0]);
1666 break;
1667 case kClose_Verb:
1668 needClose = true;
1669 break;
1670 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001671 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001672 }
1673 }
1674}
1675
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676///////////////////////////////////////////////////////////////////////////////
1677
1678void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1679 SkMatrix matrix;
1680
1681 matrix.setTranslate(dx, dy);
1682 this->transform(matrix, dst);
1683}
1684
reed@android.com8a1c16f2008-12-17 15:59:43 +00001685static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1686 int level = 2) {
1687 if (--level >= 0) {
1688 SkPoint tmp[7];
1689
1690 SkChopCubicAtHalf(pts, tmp);
1691 subdivide_cubic_to(path, &tmp[0], level);
1692 subdivide_cubic_to(path, &tmp[3], level);
1693 } else {
1694 path->cubicTo(pts[1], pts[2], pts[3]);
1695 }
1696}
1697
1698void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1699 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001700 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 dst = (SkPath*)this;
1702 }
1703
tomhudson@google.com8d430182011-06-06 19:11:19 +00001704 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 SkPath tmp;
1706 tmp.fFillType = fFillType;
1707
1708 SkPath::Iter iter(*this, false);
1709 SkPoint pts[4];
1710 SkPath::Verb verb;
1711
reed@google.com4a3b7142012-05-16 17:16:46 +00001712 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713 switch (verb) {
1714 case kMove_Verb:
1715 tmp.moveTo(pts[0]);
1716 break;
1717 case kLine_Verb:
1718 tmp.lineTo(pts[1]);
1719 break;
1720 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001721 // promote the quad to a conic
1722 tmp.conicTo(pts[1], pts[2],
1723 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001724 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001725 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001726 tmp.conicTo(pts[1], pts[2],
1727 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001728 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 case kCubic_Verb:
1730 subdivide_cubic_to(&tmp, pts);
1731 break;
1732 case kClose_Verb:
1733 tmp.close();
1734 break;
1735 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001736 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 break;
1738 }
1739 }
1740
1741 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 SkPathRef::Editor ed(&dst->fPathRef);
1743 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001744 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001746 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1747
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001750 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001751 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001753
reed026beb52015-06-10 14:23:15 -07001754 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1755 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001756 } else {
1757 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001758 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1759 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001760 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001761 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1762 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001763 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001764 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001765 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001766 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001767 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001768 }
1769 }
1770
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 SkDEBUGCODE(dst->validate();)
1772 }
1773}
1774
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775///////////////////////////////////////////////////////////////////////////////
1776///////////////////////////////////////////////////////////////////////////////
1777
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001778enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001779 kEmptyContour_SegmentState, // The current contour is empty. We may be
1780 // starting processing or we may have just
1781 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001782 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1783 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1784 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785};
1786
1787SkPath::Iter::Iter() {
1788#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001789 fPts = nullptr;
1790 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001792 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001793 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794#endif
1795 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001796 fVerbs = nullptr;
1797 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 fNeedClose = false;
1799}
1800
1801SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1802 this->setPath(path, forceClose);
1803}
1804
1805void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001806 fPts = path.fPathRef->points();
1807 fVerbs = path.fPathRef->verbs();
1808 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001809 fConicWeights = path.fPathRef->conicWeights();
1810 if (fConicWeights) {
1811 fConicWeights -= 1; // begin one behind
1812 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001814 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 fForceClose = SkToU8(forceClose);
1816 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001817 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818}
1819
1820bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001821 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 return false;
1823 }
1824 if (fForceClose) {
1825 return true;
1826 }
1827
1828 const uint8_t* verbs = fVerbs;
1829 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001830
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001831 if (kMove_Verb == *(verbs - 1)) {
1832 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 }
1834
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001835 while (verbs > stop) {
1836 // verbs points one beyond the current verb, decrement first.
1837 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001838 if (kMove_Verb == v) {
1839 break;
1840 }
1841 if (kClose_Verb == v) {
1842 return true;
1843 }
1844 }
1845 return false;
1846}
1847
1848SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001849 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001851 // A special case: if both points are NaN, SkPoint::operation== returns
1852 // false, but the iterator expects that they are treated as the same.
1853 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001854 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1855 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001856 return kClose_Verb;
1857 }
1858
reed@google.com9e25dbf2012-05-15 17:05:38 +00001859 pts[0] = fLastPt;
1860 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 fLastPt = fMoveTo;
1862 fCloseLine = true;
1863 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001864 } else {
1865 pts[0] = fMoveTo;
1866 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868}
1869
reed@google.com9e25dbf2012-05-15 17:05:38 +00001870const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 if (fSegmentState == kAfterMove_SegmentState) {
1872 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001874 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1877 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001878 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880}
1881
caryclarke8c56662015-07-14 11:19:26 -07001882void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 // We need to step over anything that will not move the current draw point
1884 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001885 const uint8_t* lastMoveVerb = nullptr;
1886 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001887 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001888 SkPoint lastPt = fLastPt;
1889 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001890 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 switch (verb) {
1892 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 // Keep a record of this most recent move
1894 lastMoveVerb = fVerbs;
1895 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001896 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001897 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001898 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001899 fPts++;
1900 break;
1901
1902 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001903 // A close when we are in a segment is always valid except when it
1904 // follows a move which follows a segment.
1905 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 return;
1907 }
1908 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001909 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 break;
1911
1912 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001913 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914 if (lastMoveVerb) {
1915 fVerbs = lastMoveVerb;
1916 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001917 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 return;
1919 }
1920 return;
1921 }
1922 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001923 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 fPts++;
1925 break;
1926
reed@google.com277c3f82013-05-31 15:17:50 +00001927 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001928 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001929 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 if (lastMoveVerb) {
1931 fVerbs = lastMoveVerb;
1932 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001933 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 return;
1935 }
1936 return;
1937 }
1938 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001939 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001940 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001941 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 break;
1943
1944 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001945 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 if (lastMoveVerb) {
1947 fVerbs = lastMoveVerb;
1948 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001949 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 return;
1951 }
1952 return;
1953 }
1954 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 fPts += 3;
1957 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001958
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001960 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 }
1962 }
1963}
1964
reed@google.com4a3b7142012-05-16 17:16:46 +00001965SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 // Close the curve if requested and if there is some curve to close
1970 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001971 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 return kLine_Verb;
1973 }
1974 fNeedClose = false;
1975 return kClose_Verb;
1976 }
1977 return kDone_Verb;
1978 }
1979
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001980 // fVerbs is one beyond the current verb, decrement first
1981 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001982 const SkPoint* SK_RESTRICT srcPts = fPts;
1983 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984
1985 switch (verb) {
1986 case kMove_Verb:
1987 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001988 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001989 verb = this->autoClose(pts);
1990 if (verb == kClose_Verb) {
1991 fNeedClose = false;
1992 }
1993 return (Verb)verb;
1994 }
1995 if (fVerbs == fVerbStop) { // might be a trailing moveto
1996 return kDone_Verb;
1997 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001998 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001999 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002001 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 fNeedClose = fForceClose;
2004 break;
2005 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002006 pts[0] = this->cons_moveTo();
2007 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008 fLastPt = srcPts[0];
2009 fCloseLine = false;
2010 srcPts += 1;
2011 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002012 case kConic_Verb:
2013 fConicWeights += 1;
2014 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002016 pts[0] = this->cons_moveTo();
2017 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 fLastPt = srcPts[1];
2019 srcPts += 2;
2020 break;
2021 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002022 pts[0] = this->cons_moveTo();
2023 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 fLastPt = srcPts[2];
2025 srcPts += 3;
2026 break;
2027 case kClose_Verb:
2028 verb = this->autoClose(pts);
2029 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002030 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 } else {
2032 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002033 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002035 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 break;
2037 }
2038 fPts = srcPts;
2039 return (Verb)verb;
2040}
2041
2042///////////////////////////////////////////////////////////////////////////////
2043
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002045 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046*/
2047
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002048size_t SkPath::writeToMemoryAsRRect(int32_t packedHeader, void* storage) const {
2049 SkRect oval;
2050 SkRRect rrect;
2051 bool isCCW;
2052 unsigned start;
2053 if (fPathRef->isOval(&oval, &isCCW, &start)) {
2054 rrect.setOval(oval);
2055 // Convert to rrect start indices.
2056 start *= 2;
2057 } else if (!fPathRef->isRRect(&rrect, &isCCW, &start)) {
2058 return false;
2059 }
2060 if (!storage) {
2061 // packed header, rrect, start index.
2062 return sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002063 }
2064
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002065 SkWBuffer buffer(storage);
2066 // Rewrite header's first direction based on rrect direction.
2067 uint8_t firstDir = isCCW ? SkPathPriv::kCCW_FirstDirection : SkPathPriv::kCW_FirstDirection;
2068 packedHeader &= ~(0x3 << kDirection_SerializationShift);
2069 packedHeader |= firstDir << kDirection_SerializationShift;
2070 packedHeader |= SerializationType::kRRect << kType_SerializationShift;
2071 buffer.write32(packedHeader);
2072 rrect.writeToBuffer(&buffer);
2073 buffer.write32(SkToS32(start));
2074 buffer.padToAlign4();
2075 return buffer.pos();
2076}
2077
2078size_t SkPath::writeToMemory(void* storage) const {
2079 SkDEBUGCODE(this->validate();)
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002080
robertphillips@google.com466310d2013-12-03 16:43:54 +00002081 int32_t packed = (fConvexity << kConvexity_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082 (fFillType << kFillType_SerializationShift) |
reed026beb52015-06-10 14:23:15 -07002083 (fFirstDirection << kDirection_SerializationShift) |
reed8f086022015-06-11 14:22:19 -07002084 (fIsVolatile << kIsVolatile_SerializationShift) |
2085 kCurrent_Version;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002086 if (size_t bytes = this->writeToMemoryAsRRect(packed, storage)) {
2087 return bytes;
2088 }
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002090 SkWBuffer buffer(storage);
2091
2092 static_assert(0 == SerializationType::kGeneral, "packed has zero in type bits");
2093 if (nullptr == storage) {
2094 // packed header, pathref, start index
2095 const int byteCount = sizeof(int32_t) * 2 + fPathRef->writeSize();
2096 return SkAlign4(byteCount);
2097 }
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002098 buffer.write32(packed);
caryclark69006412016-02-17 12:16:27 -08002099 buffer.write32(fLastMoveToIndex);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002100
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002101 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002102
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002103 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002104 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105}
2106
Mike Reed41a930f2017-07-26 17:33:44 -04002107sk_sp<SkData> SkPath::serialize() const {
2108 size_t size = this->writeToMemory(nullptr);
2109 sk_sp<SkData> data = SkData::MakeUninitialized(size);
2110 this->writeToMemory(data->writable_data());
2111 return data;
2112}
2113
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002114size_t SkPath::readFromMemory(const void* storage, size_t length) {
Mike Reed1026ccf2017-01-08 14:35:29 -05002115 SkRBuffer buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002116
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +00002117 int32_t packed;
2118 if (!buffer.readS32(&packed)) {
2119 return 0;
2120 }
2121
reed8f086022015-06-11 14:22:19 -07002122 unsigned version = packed & 0xFF;
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002123 uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
2124 FillType fillType = static_cast<FillType>((packed >> kFillType_SerializationShift) & 0x3);
2125 if (version >= kPathPrivTypeEnumVersion) {
2126 SerializationType type =
2127 static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
2128 switch (type) {
2129 case SerializationType::kRRect: {
2130 Direction rrectDir;
2131 SkRRect rrect;
2132 int32_t start;
2133 switch (dir) {
2134 case SkPathPriv::kCW_FirstDirection:
2135 rrectDir = kCW_Direction;
2136 break;
2137 case SkPathPriv::kCCW_FirstDirection:
2138 rrectDir = kCCW_Direction;
2139 break;
2140 default:
2141 return 0;
2142 }
2143 if (!rrect.readFromBuffer(&buffer)) {
2144 return 0;
2145 }
2146 if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
2147 return 0;
2148 }
2149 this->reset();
2150 this->addRRect(rrect, rrectDir, SkToUInt(start));
2151 this->setFillType(fillType);
2152 buffer.skipToAlign4();
2153 return buffer.pos();
2154 }
2155 case SerializationType::kGeneral:
2156 // Fall through to general path deserialization
2157 break;
2158 default:
2159 return 0;
2160 }
2161 }
caryclark69006412016-02-17 12:16:27 -08002162 if (version >= kPathPrivLastMoveToIndex_Version && !buffer.readS32(&fLastMoveToIndex)) {
2163 return 0;
2164 }
mtklein1b249332015-07-07 12:21:21 -07002165
Mike Kleinb9b5de52017-09-27 13:26:22 -04002166 fConvexity.store( (Convexity)((packed >> kConvexity_SerializationShift) & 0xFF) );
Brian Salomon1e3b79e2017-09-21 12:29:24 -04002167 fFillType = fillType;
jvanverthb3eb6872014-10-24 07:12:51 -07002168 fIsVolatile = (packed >> kIsVolatile_SerializationShift) & 0x1;
commit-bot@chromium.orgfed2ab62014-01-23 15:16:05 +00002169 SkPathRef* pathRef = SkPathRef::CreateFromBuffer(&buffer);
ajumaf8aec582016-01-13 13:46:31 -08002170 if (!pathRef) {
2171 return 0;
2172 }
2173
2174 fPathRef.reset(pathRef);
2175 SkDEBUGCODE(this->validate();)
2176 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002177
reed8f086022015-06-11 14:22:19 -07002178 // compatibility check
2179 if (version < kPathPrivFirstDirection_Version) {
2180 switch (dir) { // old values
2181 case 0:
2182 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
2183 break;
2184 case 1:
2185 fFirstDirection = SkPathPriv::kCW_FirstDirection;
2186 break;
2187 case 2:
2188 fFirstDirection = SkPathPriv::kCCW_FirstDirection;
2189 break;
2190 default:
Adrienne Walkerffbdd022017-09-22 10:43:26 -07002191 return 0;
reed8f086022015-06-11 14:22:19 -07002192 }
2193 } else {
2194 fFirstDirection = dir;
2195 }
2196
ajumaf8aec582016-01-13 13:46:31 -08002197 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002198}
2199
2200///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002201
Ben Wagner4d1955c2017-03-10 13:08:15 -05002202#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002203#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002204#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002205
reed@google.com51bbe752013-01-17 22:07:50 +00002206static void append_params(SkString* str, const char label[], const SkPoint pts[],
reede05fed02014-12-15 07:59:53 -08002207 int count, SkScalarAsStringType strType, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002208 str->append(label);
2209 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002210
reed@google.com51bbe752013-01-17 22:07:50 +00002211 const SkScalar* values = &pts[0].fX;
2212 count *= 2;
2213
2214 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002215 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002216 if (i < count - 1) {
2217 str->append(", ");
2218 }
2219 }
reed@google.com277c3f82013-05-31 15:17:50 +00002220 if (conicWeight >= 0) {
2221 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002222 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002223 }
caryclark08fa28c2014-10-23 13:08:56 -07002224 str->append(");");
reede05fed02014-12-15 07:59:53 -08002225 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002226 str->append(" // ");
2227 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002228 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002229 if (i < count - 1) {
2230 str->append(", ");
2231 }
2232 }
2233 if (conicWeight >= 0) {
2234 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002235 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002236 }
2237 }
2238 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002239}
2240
caryclarke9562592014-09-15 09:26:09 -07002241void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002242 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002243 Iter iter(*this, forceClose);
2244 SkPoint pts[4];
2245 Verb verb;
2246
reed@google.com51bbe752013-01-17 22:07:50 +00002247 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002248 char const * const gFillTypeStrs[] = {
2249 "Winding",
2250 "EvenOdd",
2251 "InverseWinding",
2252 "InverseEvenOdd",
2253 };
2254 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2255 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002256 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002257 switch (verb) {
2258 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002259 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002260 break;
2261 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002262 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002263 break;
2264 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002265 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002266 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002267 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002268 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002269 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002270 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002271 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002272 break;
2273 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002274 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002275 break;
2276 default:
2277 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2278 verb = kDone_Verb; // stop the loop
2279 break;
2280 }
caryclark1049f122015-04-20 08:31:59 -07002281 if (!wStream && builder.size()) {
2282 SkDebugf("%s", builder.c_str());
2283 builder.reset();
2284 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002285 }
caryclark66a5d8b2014-06-24 08:30:15 -07002286 if (wStream) {
2287 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002289}
2290
reed@android.come522ca52009-11-23 20:10:41 +00002291void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002292 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002293}
2294
2295void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002296 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002297}
2298
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002299
Cary Clark0461e9f2017-08-25 15:13:38 -04002300bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002301 if ((fFillType & ~3) != 0) {
2302 return false;
2303 }
reed@google.comabf15c12011-01-18 20:35:51 +00002304
djsollen@google.com077348c2012-10-22 20:23:32 +00002305#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002306 if (!fBoundsIsDirty) {
2307 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002308
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002309 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002310 if (SkToBool(fIsFinite) != isFinite) {
2311 return false;
2312 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002313
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002314 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002315 // if we're empty, fBounds may be empty but translated, so we can't
2316 // necessarily compare to bounds directly
2317 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2318 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002319 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2320 return false;
2321 }
reed@android.come522ca52009-11-23 20:10:41 +00002322 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002323 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002324 if (!fBounds.isEmpty()) {
2325 return false;
2326 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002327 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002328 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002329 if (!fBounds.contains(bounds)) {
2330 return false;
2331 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002332 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002333 }
reed@android.come522ca52009-11-23 20:10:41 +00002334 }
2335 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002336#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002337 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002338}
reed@android.come522ca52009-11-23 20:10:41 +00002339
reed@google.com04863fa2011-05-15 04:08:24 +00002340///////////////////////////////////////////////////////////////////////////////
2341
reed@google.com0b7b9822011-05-16 12:29:27 +00002342static int sign(SkScalar x) { return x < 0; }
2343#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002344
robertphillipsc506e302014-09-16 09:43:31 -07002345enum DirChange {
2346 kLeft_DirChange,
2347 kRight_DirChange,
2348 kStraight_DirChange,
2349 kBackwards_DirChange,
2350
2351 kInvalid_DirChange
2352};
2353
2354
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002355static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002356 // The error epsilon was empirically derived; worse case round rects
2357 // with a mid point outset by 2x float epsilon in tests had an error
2358 // of 12.
2359 const int epsilon = 16;
2360 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2361 return false;
2362 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002363 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002364 int aBits = SkFloatAs2sCompliment(compA);
2365 int bBits = SkFloatAs2sCompliment(compB);
2366 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002367}
2368
caryclarkb4216502015-03-02 13:02:34 -08002369static bool approximately_zero_when_compared_to(double x, double y) {
2370 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002371}
2372
caryclarkb4216502015-03-02 13:02:34 -08002373
reed@google.com04863fa2011-05-15 04:08:24 +00002374// only valid for a single contour
2375struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002376 Convexicator()
2377 : fPtCount(0)
2378 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002379 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002380 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002381 , fIsCurve(false)
2382 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002383 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002384 // warnings
djsollen2f124632016-04-29 13:53:05 -07002385 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002386 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002387 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002388 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002389 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002390
2391 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002392 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002393 }
2394
2395 SkPath::Convexity getConvexity() const { return fConvexity; }
2396
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002397 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002398 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002399
reed@google.com04863fa2011-05-15 04:08:24 +00002400 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002401 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002402 return;
2403 }
2404
2405 if (0 == fPtCount) {
2406 fCurrPt = pt;
2407 ++fPtCount;
2408 } else {
2409 SkVector vec = pt - fCurrPt;
caryclarkd3d1a982014-12-08 04:57:38 -08002410 SkScalar lengthSqd = vec.lengthSqd();
2411 if (!SkScalarIsFinite(lengthSqd)) {
2412 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002413 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002414 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002415 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002416 fCurrPt = pt;
2417 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002418 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002419 } else {
2420 SkASSERT(fPtCount > 2);
2421 this->addVec(vec);
2422 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002423
reed@google.com85b6e392011-05-15 20:25:17 +00002424 int sx = sign(vec.fX);
2425 int sy = sign(vec.fY);
2426 fDx += (sx != fSx);
2427 fDy += (sy != fSy);
2428 fSx = sx;
2429 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002430
reed@google.com85b6e392011-05-15 20:25:17 +00002431 if (fDx > 3 || fDy > 3) {
2432 fConvexity = SkPath::kConcave_Convexity;
2433 }
reed@google.com04863fa2011-05-15 04:08:24 +00002434 }
2435 }
2436 }
2437
2438 void close() {
2439 if (fPtCount > 2) {
2440 this->addVec(fFirstVec);
2441 }
2442 }
2443
caryclarkb4216502015-03-02 13:02:34 -08002444 DirChange directionChange(const SkVector& curVec) {
2445 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2446
2447 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2448 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2449 largest = SkTMax(largest, -smallest);
2450
2451 if (!almost_equal(largest, largest + cross)) {
2452 int sign = SkScalarSignAsInt(cross);
2453 if (sign) {
2454 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2455 }
2456 }
2457
2458 if (cross) {
2459 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2460 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2461 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2462 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2463 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2464 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2465 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2466 if (sign) {
2467 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2468 }
2469 }
2470 }
2471
2472 if (!SkScalarNearlyZero(fLastVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2473 !SkScalarNearlyZero(curVec.lengthSqd(), SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2474 fLastVec.dot(curVec) < 0.0f) {
2475 return kBackwards_DirChange;
2476 }
2477
2478 return kStraight_DirChange;
2479 }
2480
Cary Clarkc8323aa2017-08-25 08:04:43 -04002481 bool hasBackwards() const {
2482 return fBackwards;
2483 }
caryclarkb4216502015-03-02 13:02:34 -08002484
caryclarkd3d1a982014-12-08 04:57:38 -08002485 bool isFinite() const {
2486 return fIsFinite;
2487 }
2488
caryclark5ccef572015-03-02 10:07:56 -08002489 void setCurve(bool isCurve) {
2490 fIsCurve = isCurve;
2491 }
2492
reed@google.com04863fa2011-05-15 04:08:24 +00002493private:
2494 void addVec(const SkVector& vec) {
2495 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002496 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002497 switch (dir) {
2498 case kLeft_DirChange: // fall through
2499 case kRight_DirChange:
2500 if (kInvalid_DirChange == fExpectedDir) {
2501 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002502 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2503 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002504 } else if (dir != fExpectedDir) {
2505 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002506 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002507 }
2508 fLastVec = vec;
2509 break;
2510 case kStraight_DirChange:
2511 break;
2512 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002513 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002514 // If any of the subsequent dir is non-backward, it'll be concave.
2515 // Otherwise, it's still convex.
2516 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002517 }
robertphillipsc506e302014-09-16 09:43:31 -07002518 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002519 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002520 break;
2521 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002522 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002523 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002524 }
2525 }
2526
caryclarkb4216502015-03-02 13:02:34 -08002527 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002528 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002529 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002530 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2531 // value with the current vec is deemed to be of a significant value.
2532 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002533 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002534 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002535 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002536 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002537 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002538 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002539 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002540 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002541};
2542
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002543SkPath::Convexity SkPath::internalGetConvexity() const {
2544 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002545 SkPoint pts[4];
2546 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002547 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002548
2549 int contourCount = 0;
2550 int count;
2551 Convexicator state;
2552
caryclarkd3d1a982014-12-08 04:57:38 -08002553 if (!isFinite()) {
2554 return kUnknown_Convexity;
2555 }
Brian Osman205a1262017-09-18 13:13:48 +00002556 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002557 switch (verb) {
2558 case kMove_Verb:
2559 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002560 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002561 return kConcave_Convexity;
2562 }
2563 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002564 // fall through
2565 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002566 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002567 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002568 break;
caryclark5ccef572015-03-02 10:07:56 -08002569 case kQuad_Verb:
2570 // fall through
2571 case kConic_Verb:
2572 // fall through
2573 case kCubic_Verb:
2574 count = 2 + (kCubic_Verb == verb);
2575 // As an additional enhancement, this could set curve true only
2576 // if the curve is nonlinear
2577 state.setCurve(true);
2578 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002579 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002580 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002581 state.close();
2582 count = 0;
2583 break;
2584 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002585 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002586 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002587 return kConcave_Convexity;
2588 }
2589
2590 for (int i = 1; i <= count; i++) {
2591 state.addPt(pts[i]);
2592 }
2593 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002594 if (!state.isFinite()) {
2595 return kUnknown_Convexity;
2596 }
reed@google.com04863fa2011-05-15 04:08:24 +00002597 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002598 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002599 return kConcave_Convexity;
2600 }
2601 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002602 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002603 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002604 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2605 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2606 fConvexity = Convexity::kConcave_Convexity;
2607 } else {
2608 fFirstDirection = state.getFirstDirection();
2609 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002610 }
2611 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002612}
reed@google.com69a99432012-01-10 18:00:10 +00002613
2614///////////////////////////////////////////////////////////////////////////////
2615
2616class ContourIter {
2617public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002618 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002619
2620 bool done() const { return fDone; }
2621 // if !done() then these may be called
2622 int count() const { return fCurrPtCount; }
2623 const SkPoint* pts() const { return fCurrPt; }
2624 void next();
2625
2626private:
2627 int fCurrPtCount;
2628 const SkPoint* fCurrPt;
2629 const uint8_t* fCurrVerb;
2630 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002631 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002632 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002633 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002634};
2635
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002636ContourIter::ContourIter(const SkPathRef& pathRef) {
2637 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002638 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002639 fCurrPt = pathRef.points();
2640 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002641 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002642 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002643 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002644 this->next();
2645}
2646
2647void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002648 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002649 fDone = true;
2650 }
2651 if (fDone) {
2652 return;
2653 }
2654
2655 // skip pts of prev contour
2656 fCurrPt += fCurrPtCount;
2657
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002658 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002659 int ptCount = 1; // moveTo
2660 const uint8_t* verbs = fCurrVerb;
2661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002662 for (--verbs; verbs > fStopVerbs; --verbs) {
2663 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002664 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002665 goto CONTOUR_END;
2666 case SkPath::kLine_Verb:
2667 ptCount += 1;
2668 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002669 case SkPath::kConic_Verb:
2670 fCurrConicWeight += 1;
2671 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002672 case SkPath::kQuad_Verb:
2673 ptCount += 2;
2674 break;
2675 case SkPath::kCubic_Verb:
2676 ptCount += 3;
2677 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002678 case SkPath::kClose_Verb:
2679 break;
2680 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002681 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002682 break;
2683 }
2684 }
2685CONTOUR_END:
2686 fCurrPtCount = ptCount;
2687 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002688 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002689}
2690
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002691// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002692static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002693 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2694 // We may get 0 when the above subtracts underflow. We expect this to be
2695 // very rare and lazily promote to double.
2696 if (0 == cross) {
2697 double p0x = SkScalarToDouble(p0.fX);
2698 double p0y = SkScalarToDouble(p0.fY);
2699
2700 double p1x = SkScalarToDouble(p1.fX);
2701 double p1y = SkScalarToDouble(p1.fY);
2702
2703 double p2x = SkScalarToDouble(p2.fX);
2704 double p2y = SkScalarToDouble(p2.fY);
2705
2706 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2707 (p1y - p0y) * (p2x - p0x));
2708
2709 }
2710 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002711}
2712
reed@google.comc1ea60a2012-01-31 15:15:36 +00002713// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002714static int find_max_y(const SkPoint pts[], int count) {
2715 SkASSERT(count > 0);
2716 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002717 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002718 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002719 SkScalar y = pts[i].fY;
2720 if (y > max) {
2721 max = y;
2722 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002723 }
2724 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002725 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002726}
2727
reed@google.comcabaf1d2012-01-11 21:03:05 +00002728static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2729 int i = index;
2730 for (;;) {
2731 i = (i + inc) % n;
2732 if (i == index) { // we wrapped around, so abort
2733 break;
2734 }
2735 if (pts[index] != pts[i]) { // found a different point, success!
2736 break;
2737 }
2738 }
2739 return i;
2740}
2741
reed@google.comc1ea60a2012-01-31 15:15:36 +00002742/**
2743 * Starting at index, and moving forward (incrementing), find the xmin and
2744 * xmax of the contiguous points that have the same Y.
2745 */
2746static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2747 int* maxIndexPtr) {
2748 const SkScalar y = pts[index].fY;
2749 SkScalar min = pts[index].fX;
2750 SkScalar max = min;
2751 int minIndex = index;
2752 int maxIndex = index;
2753 for (int i = index + 1; i < n; ++i) {
2754 if (pts[i].fY != y) {
2755 break;
2756 }
2757 SkScalar x = pts[i].fX;
2758 if (x < min) {
2759 min = x;
2760 minIndex = i;
2761 } else if (x > max) {
2762 max = x;
2763 maxIndex = i;
2764 }
2765 }
2766 *maxIndexPtr = maxIndex;
2767 return minIndex;
2768}
2769
reed026beb52015-06-10 14:23:15 -07002770static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2771 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002772}
2773
reed@google.comac8543f2012-01-30 20:51:25 +00002774/*
2775 * We loop through all contours, and keep the computed cross-product of the
2776 * contour that contained the global y-max. If we just look at the first
2777 * contour, we may find one that is wound the opposite way (correctly) since
2778 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2779 * that is outer most (or at least has the global y-max) before we can consider
2780 * its cross product.
2781 */
reed026beb52015-06-10 14:23:15 -07002782bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002783 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2784 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002785 return true;
2786 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002787
2788 // don't want to pay the cost for computing this if it
2789 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002790 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2791 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002792 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002793 return false;
2794 }
reed@google.com69a99432012-01-10 18:00:10 +00002795
reed026beb52015-06-10 14:23:15 -07002796 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002797
reed@google.comac8543f2012-01-30 20:51:25 +00002798 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002799 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002800 SkScalar ymaxCross = 0;
2801
reed@google.com69a99432012-01-10 18:00:10 +00002802 for (; !iter.done(); iter.next()) {
2803 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002804 if (n < 3) {
2805 continue;
2806 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002807
reed@google.comcabaf1d2012-01-11 21:03:05 +00002808 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002809 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002810 int index = find_max_y(pts, n);
2811 if (pts[index].fY < ymax) {
2812 continue;
2813 }
2814
2815 // If there is more than 1 distinct point at the y-max, we take the
2816 // x-min and x-max of them and just subtract to compute the dir.
2817 if (pts[(index + 1) % n].fY == pts[index].fY) {
2818 int maxIndex;
2819 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2820 if (minIndex == maxIndex) {
2821 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002822 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002823 SkASSERT(pts[minIndex].fY == pts[index].fY);
2824 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2825 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2826 // we just subtract the indices, and let that auto-convert to
2827 // SkScalar, since we just want - or + to signal the direction.
2828 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002829 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002830 TRY_CROSSPROD:
2831 // Find a next and prev index to use for the cross-product test,
2832 // but we try to find pts that form non-zero vectors from pts[index]
2833 //
2834 // Its possible that we can't find two non-degenerate vectors, so
2835 // we have to guard our search (e.g. all the pts could be in the
2836 // same place).
2837
2838 // we pass n - 1 instead of -1 so we don't foul up % operator by
2839 // passing it a negative LH argument.
2840 int prev = find_diff_pt(pts, index, n, n - 1);
2841 if (prev == index) {
2842 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002843 continue;
2844 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002845 int next = find_diff_pt(pts, index, n, 1);
2846 SkASSERT(next != index);
2847 cross = cross_prod(pts[prev], pts[index], pts[next]);
2848 // if we get a zero and the points are horizontal, then we look at the spread in
2849 // x-direction. We really should continue to walk away from the degeneracy until
2850 // there is a divergence.
2851 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2852 // construct the subtract so we get the correct Direction below
2853 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002854 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002855 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002856
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002857 if (cross) {
2858 // record our best guess so far
2859 ymax = pts[index].fY;
2860 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002861 }
2862 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002863 if (ymaxCross) {
2864 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002865 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002866 return true;
2867 } else {
2868 return false;
2869 }
reed@google.comac8543f2012-01-30 20:51:25 +00002870}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002871
2872///////////////////////////////////////////////////////////////////////////////
2873
caryclark9aacd902015-12-14 08:38:09 -08002874static bool between(SkScalar a, SkScalar b, SkScalar c) {
2875 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2876 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2877 return (a - b) * (c - b) <= 0;
2878}
2879
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2881 SkScalar t) {
2882 SkScalar A = c3 + 3*(c1 - c2) - c0;
2883 SkScalar B = 3*(c2 - c1 - c1 + c0);
2884 SkScalar C = 3*(c1 - c0);
2885 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002886 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002887}
2888
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889template <size_t N> static void find_minmax(const SkPoint pts[],
2890 SkScalar* minPtr, SkScalar* maxPtr) {
2891 SkScalar min, max;
2892 min = max = pts[0].fX;
2893 for (size_t i = 1; i < N; ++i) {
2894 min = SkMinScalar(min, pts[i].fX);
2895 max = SkMaxScalar(max, pts[i].fX);
2896 }
2897 *minPtr = min;
2898 *maxPtr = max;
2899}
2900
caryclark9cb5d752015-12-18 04:35:24 -08002901static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2902 if (start.fY == end.fY) {
2903 return between(start.fX, x, end.fX) && x != end.fX;
2904 } else {
2905 return x == start.fX && y == start.fY;
2906 }
2907}
2908
caryclark9aacd902015-12-14 08:38:09 -08002909static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002910 SkScalar y0 = pts[0].fY;
2911 SkScalar y3 = pts[3].fY;
2912
2913 int dir = 1;
2914 if (y0 > y3) {
2915 SkTSwap(y0, y3);
2916 dir = -1;
2917 }
2918 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002919 return 0;
2920 }
caryclark9cb5d752015-12-18 04:35:24 -08002921 if (checkOnCurve(x, y, pts[0], pts[3])) {
2922 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002923 return 0;
2924 }
caryclark9cb5d752015-12-18 04:35:24 -08002925 if (y == y3) {
2926 return 0;
2927 }
2928
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 // quickreject or quickaccept
2930 SkScalar min, max;
2931 find_minmax<4>(pts, &min, &max);
2932 if (x < min) {
2933 return 0;
2934 }
2935 if (x > max) {
2936 return dir;
2937 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002938
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002940 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002941 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002942 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002943 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002944 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002945 if (SkScalarNearlyEqual(xt, x)) {
2946 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2947 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002948 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002949 }
2950 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 return xt < x ? dir : 0;
2952}
2953
caryclark9aacd902015-12-14 08:38:09 -08002954static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 SkPoint dst[10];
2956 int n = SkChopCubicAtYExtrema(pts, dst);
2957 int w = 0;
2958 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002959 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002960 }
2961 return w;
2962}
2963
caryclark9aacd902015-12-14 08:38:09 -08002964static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2965 SkASSERT(src);
2966 SkASSERT(t >= 0 && t <= 1);
2967 SkScalar src2w = src[2] * w;
2968 SkScalar C = src[0];
2969 SkScalar A = src[4] - 2 * src2w + C;
2970 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002971 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002972}
2973
2974
2975static double conic_eval_denominator(SkScalar w, SkScalar t) {
2976 SkScalar B = 2 * (w - 1);
2977 SkScalar C = 1;
2978 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002979 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002980}
2981
2982static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2983 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002984 SkScalar y0 = pts[0].fY;
2985 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002986
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002987 int dir = 1;
2988 if (y0 > y2) {
2989 SkTSwap(y0, y2);
2990 dir = -1;
2991 }
caryclark9aacd902015-12-14 08:38:09 -08002992 if (y < y0 || y > y2) {
2993 return 0;
2994 }
caryclark9cb5d752015-12-18 04:35:24 -08002995 if (checkOnCurve(x, y, pts[0], pts[2])) {
2996 *onCurveCount += 1;
2997 return 0;
2998 }
caryclark9aacd902015-12-14 08:38:09 -08002999 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 return 0;
3001 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003002
caryclark9aacd902015-12-14 08:38:09 -08003003 SkScalar roots[2];
3004 SkScalar A = pts[2].fY;
3005 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
3006 SkScalar C = pts[0].fY;
3007 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3008 B -= C; // B = b*w - w * yCept + yCept - a
3009 C -= y;
3010 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3011 SkASSERT(n <= 1);
3012 SkScalar xt;
3013 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003014 // zero roots are returned only when y0 == y
3015 // Need [0] if dir == 1
3016 // and [2] if dir == -1
3017 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08003018 } else {
3019 SkScalar t = roots[0];
3020 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
3021 }
3022 if (SkScalarNearlyEqual(xt, x)) {
3023 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3024 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003025 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003026 }
3027 }
3028 return xt < x ? dir : 0;
3029}
3030
3031static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
3032 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
3033 if (y0 == y1) {
3034 return true;
3035 }
3036 if (y0 < y1) {
3037 return y1 <= y2;
3038 } else {
3039 return y1 >= y2;
3040 }
3041}
3042
3043static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
3044 int* onCurveCount) {
3045 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08003046 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08003047 // If the data points are very large, the conic may not be monotonic but may also
3048 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08003049 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
3050 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
3051 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08003052 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
3053 }
3054 return w;
3055}
3056
3057static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
3058 SkScalar y0 = pts[0].fY;
3059 SkScalar y2 = pts[2].fY;
3060
3061 int dir = 1;
3062 if (y0 > y2) {
3063 SkTSwap(y0, y2);
3064 dir = -1;
3065 }
3066 if (y < y0 || y > y2) {
3067 return 0;
3068 }
caryclark9cb5d752015-12-18 04:35:24 -08003069 if (checkOnCurve(x, y, pts[0], pts[2])) {
3070 *onCurveCount += 1;
3071 return 0;
3072 }
caryclark9aacd902015-12-14 08:38:09 -08003073 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003074 return 0;
3075 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003076 // bounds check on X (not required. is it faster?)
3077#if 0
3078 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3079 return 0;
3080 }
3081#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003082
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003083 SkScalar roots[2];
3084 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3085 2 * (pts[1].fY - pts[0].fY),
3086 pts[0].fY - y,
3087 roots);
3088 SkASSERT(n <= 1);
3089 SkScalar xt;
3090 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003091 // zero roots are returned only when y0 == y
3092 // Need [0] if dir == 1
3093 // and [2] if dir == -1
3094 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003095 } else {
3096 SkScalar t = roots[0];
3097 SkScalar C = pts[0].fX;
3098 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3099 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003100 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003101 }
caryclark9aacd902015-12-14 08:38:09 -08003102 if (SkScalarNearlyEqual(xt, x)) {
3103 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3104 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003105 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003106 }
3107 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003108 return xt < x ? dir : 0;
3109}
3110
caryclark9aacd902015-12-14 08:38:09 -08003111static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003112 SkPoint dst[5];
3113 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003114
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003115 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3116 n = SkChopQuadAtYExtrema(pts, dst);
3117 pts = dst;
3118 }
caryclark9aacd902015-12-14 08:38:09 -08003119 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003120 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003121 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003122 }
3123 return w;
3124}
3125
caryclark9aacd902015-12-14 08:38:09 -08003126static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003127 SkScalar x0 = pts[0].fX;
3128 SkScalar y0 = pts[0].fY;
3129 SkScalar x1 = pts[1].fX;
3130 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003131
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003132 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003133
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003134 int dir = 1;
3135 if (y0 > y1) {
3136 SkTSwap(y0, y1);
3137 dir = -1;
3138 }
caryclark9aacd902015-12-14 08:38:09 -08003139 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003140 return 0;
3141 }
caryclark9cb5d752015-12-18 04:35:24 -08003142 if (checkOnCurve(x, y, pts[0], pts[1])) {
3143 *onCurveCount += 1;
3144 return 0;
3145 }
3146 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003147 return 0;
3148 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003149 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003150
caryclark9aacd902015-12-14 08:38:09 -08003151 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003152 // zero cross means the point is on the line, and since the case where
3153 // y of the query point is at the end point is handled above, we can be
3154 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003155 if (x != x1 || y != pts[1].fY) {
3156 *onCurveCount += 1;
3157 }
caryclark9aacd902015-12-14 08:38:09 -08003158 dir = 0;
3159 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003160 dir = 0;
3161 }
3162 return dir;
3163}
3164
caryclark9aacd902015-12-14 08:38:09 -08003165static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3166 SkTDArray<SkVector>* tangents) {
3167 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3168 && !between(pts[2].fY, y, pts[3].fY)) {
3169 return;
3170 }
3171 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3172 && !between(pts[2].fX, x, pts[3].fX)) {
3173 return;
3174 }
3175 SkPoint dst[10];
3176 int n = SkChopCubicAtYExtrema(pts, dst);
3177 for (int i = 0; i <= n; ++i) {
3178 SkPoint* c = &dst[i * 3];
3179 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003180 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003181 continue;
mbarbella276e6332016-05-31 14:44:01 -07003182 }
caryclark9aacd902015-12-14 08:38:09 -08003183 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3184 if (!SkScalarNearlyEqual(x, xt)) {
3185 continue;
3186 }
3187 SkVector tangent;
3188 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3189 tangents->push(tangent);
3190 }
3191}
3192
3193static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3194 SkTDArray<SkVector>* tangents) {
3195 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3196 return;
3197 }
3198 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3199 return;
3200 }
3201 SkScalar roots[2];
3202 SkScalar A = pts[2].fY;
3203 SkScalar B = pts[1].fY * w - y * w + y;
3204 SkScalar C = pts[0].fY;
3205 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3206 B -= C; // B = b*w - w * yCept + yCept - a
3207 C -= y;
3208 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3209 for (int index = 0; index < n; ++index) {
3210 SkScalar t = roots[index];
3211 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3212 if (!SkScalarNearlyEqual(x, xt)) {
3213 continue;
3214 }
3215 SkConic conic(pts, w);
3216 tangents->push(conic.evalTangentAt(t));
3217 }
3218}
3219
3220static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3221 SkTDArray<SkVector>* tangents) {
3222 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3223 return;
3224 }
3225 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3226 return;
3227 }
3228 SkScalar roots[2];
3229 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3230 2 * (pts[1].fY - pts[0].fY),
3231 pts[0].fY - y,
3232 roots);
3233 for (int index = 0; index < n; ++index) {
3234 SkScalar t = roots[index];
3235 SkScalar C = pts[0].fX;
3236 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3237 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003238 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003239 if (!SkScalarNearlyEqual(x, xt)) {
3240 continue;
3241 }
3242 tangents->push(SkEvalQuadTangentAt(pts, t));
3243 }
3244}
3245
3246static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3247 SkTDArray<SkVector>* tangents) {
3248 SkScalar y0 = pts[0].fY;
3249 SkScalar y1 = pts[1].fY;
3250 if (!between(y0, y, y1)) {
3251 return;
3252 }
3253 SkScalar x0 = pts[0].fX;
3254 SkScalar x1 = pts[1].fX;
3255 if (!between(x0, x, x1)) {
3256 return;
3257 }
3258 SkScalar dx = x1 - x0;
3259 SkScalar dy = y1 - y0;
3260 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3261 return;
3262 }
3263 SkVector v;
3264 v.set(dx, dy);
3265 tangents->push(v);
3266}
3267
reed@google.com4db592c2013-10-30 17:39:43 +00003268static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3269 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3270}
3271
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003272bool SkPath::contains(SkScalar x, SkScalar y) const {
3273 bool isInverse = this->isInverseFillType();
3274 if (this->isEmpty()) {
3275 return isInverse;
3276 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003277
reed@google.com4db592c2013-10-30 17:39:43 +00003278 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003279 return isInverse;
3280 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003281
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003282 SkPath::Iter iter(*this, true);
3283 bool done = false;
3284 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003285 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003286 do {
3287 SkPoint pts[4];
3288 switch (iter.next(pts, false)) {
3289 case SkPath::kMove_Verb:
3290 case SkPath::kClose_Verb:
3291 break;
3292 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003293 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003294 break;
3295 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003296 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003297 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003298 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003299 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003300 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003301 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003302 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003303 break;
3304 case SkPath::kDone_Verb:
3305 done = true;
3306 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003307 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003308 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003309 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3310 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3311 if (evenOddFill) {
3312 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003313 }
caryclark9aacd902015-12-14 08:38:09 -08003314 if (w) {
3315 return !isInverse;
3316 }
3317 if (onCurveCount <= 1) {
3318 return SkToBool(onCurveCount) ^ isInverse;
3319 }
3320 if ((onCurveCount & 1) || evenOddFill) {
3321 return SkToBool(onCurveCount & 1) ^ isInverse;
3322 }
halcanary9d524f22016-03-29 09:03:52 -07003323 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003324 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3325 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003326 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003327 SkTDArray<SkVector> tangents;
3328 do {
3329 SkPoint pts[4];
3330 int oldCount = tangents.count();
3331 switch (iter.next(pts, false)) {
3332 case SkPath::kMove_Verb:
3333 case SkPath::kClose_Verb:
3334 break;
3335 case SkPath::kLine_Verb:
3336 tangent_line(pts, x, y, &tangents);
3337 break;
3338 case SkPath::kQuad_Verb:
3339 tangent_quad(pts, x, y, &tangents);
3340 break;
3341 case SkPath::kConic_Verb:
3342 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3343 break;
3344 case SkPath::kCubic_Verb:
3345 tangent_cubic(pts, x, y, &tangents);
3346 break;
3347 case SkPath::kDone_Verb:
3348 done = true;
3349 break;
3350 }
3351 if (tangents.count() > oldCount) {
3352 int last = tangents.count() - 1;
3353 const SkVector& tangent = tangents[last];
3354 if (SkScalarNearlyZero(tangent.lengthSqd())) {
3355 tangents.remove(last);
3356 } else {
3357 for (int index = 0; index < last; ++index) {
3358 const SkVector& test = tangents[index];
3359 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003360 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3361 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003362 tangents.remove(last);
3363 tangents.removeShuffle(index);
3364 break;
3365 }
3366 }
3367 }
3368 }
3369 } while (!done);
3370 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003371}
fmalitaaa0df4e2015-12-01 09:13:23 -08003372
3373int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3374 SkScalar w, SkPoint pts[], int pow2) {
3375 const SkConic conic(p0, p1, p2, w);
3376 return conic.chopIntoQuadsPOW2(pts, pow2);
3377}
bsalomonedc743a2016-06-01 09:42:31 -07003378
3379bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3380 unsigned* start) {
3381 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3382 return false;
3383 }
3384 SkPath::RawIter iter(path);
3385 SkPoint verbPts[4];
3386 SkPath::Verb v;
3387 SkPoint rectPts[5];
3388 int rectPtCnt = 0;
3389 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3390 switch (v) {
3391 case SkPath::kMove_Verb:
3392 if (0 != rectPtCnt) {
3393 return false;
3394 }
3395 rectPts[0] = verbPts[0];
3396 ++rectPtCnt;
3397 break;
3398 case SkPath::kLine_Verb:
3399 if (5 == rectPtCnt) {
3400 return false;
3401 }
3402 rectPts[rectPtCnt] = verbPts[1];
3403 ++rectPtCnt;
3404 break;
3405 case SkPath::kClose_Verb:
3406 if (4 == rectPtCnt) {
3407 rectPts[4] = rectPts[0];
3408 rectPtCnt = 5;
3409 }
3410 break;
3411 default:
3412 return false;
3413 }
3414 }
3415 if (rectPtCnt < 5) {
3416 return false;
3417 }
3418 if (rectPts[0] != rectPts[4]) {
3419 return false;
3420 }
bsalomon057ae8a2016-07-24 05:37:26 -07003421 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3422 // and pts 1 and 2 the opposite vertical or horizontal edge).
3423 bool vec03IsVertical;
3424 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3425 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3426 // Make sure it has non-zero width and height
3427 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003428 return false;
3429 }
bsalomon057ae8a2016-07-24 05:37:26 -07003430 vec03IsVertical = true;
3431 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3432 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3433 // Make sure it has non-zero width and height
3434 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3435 return false;
3436 }
3437 vec03IsVertical = false;
3438 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003439 return false;
3440 }
bsalomon057ae8a2016-07-24 05:37:26 -07003441 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3442 // set if it is on the bottom edge.
3443 unsigned sortFlags =
3444 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3445 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3446 switch (sortFlags) {
3447 case 0b00:
3448 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3449 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3450 *start = 0;
3451 break;
3452 case 0b01:
3453 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3454 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3455 *start = 1;
3456 break;
3457 case 0b10:
3458 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3459 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3460 *start = 3;
3461 break;
3462 case 0b11:
3463 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3464 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3465 *start = 2;
3466 break;
bsalomonedc743a2016-06-01 09:42:31 -07003467 }
bsalomonedc743a2016-06-01 09:42:31 -07003468 return true;
3469}
bsalomon21af9ca2016-08-25 12:29:23 -07003470
3471void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3472 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3473 SkASSERT(!oval.isEmpty());
3474 SkASSERT(sweepAngle);
3475
3476 path->reset();
3477 path->setIsVolatile(true);
3478 path->setFillType(SkPath::kWinding_FillType);
3479 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3480 path->addOval(oval);
3481 return;
3482 }
3483 if (useCenter) {
3484 path->moveTo(oval.centerX(), oval.centerY());
3485 }
3486 // Arc to mods at 360 and drawArc is not supposed to.
3487 bool forceMoveTo = !useCenter;
3488 while (sweepAngle <= -360.f) {
3489 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3490 startAngle -= 180.f;
3491 path->arcTo(oval, startAngle, -180.f, false);
3492 startAngle -= 180.f;
3493 forceMoveTo = false;
3494 sweepAngle += 360.f;
3495 }
3496 while (sweepAngle >= 360.f) {
3497 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3498 startAngle += 180.f;
3499 path->arcTo(oval, startAngle, 180.f, false);
3500 startAngle += 180.f;
3501 forceMoveTo = false;
3502 sweepAngle -= 360.f;
3503 }
3504 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3505 if (useCenter) {
3506 path->close();
3507 }
3508}
Mike Reed0d7dac82017-02-02 17:45:56 -08003509
3510///////////////////////////////////////////////////////////////////////////////////////////////////
3511#include "SkNx.h"
3512
3513static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3514 SkScalar ts[2];
3515 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3516 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3517 SkASSERT(n >= 0 && n <= 2);
3518 for (int i = 0; i < n; ++i) {
3519 extremas[i] = SkEvalQuadAt(src, ts[i]);
3520 }
3521 extremas[n] = src[2];
3522 return n + 1;
3523}
3524
3525static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3526 SkConic conic(src[0], src[1], src[2], w);
3527 SkScalar ts[2];
3528 int n = conic.findXExtrema(ts);
3529 n += conic.findYExtrema(&ts[n]);
3530 SkASSERT(n >= 0 && n <= 2);
3531 for (int i = 0; i < n; ++i) {
3532 extremas[i] = conic.evalAt(ts[i]);
3533 }
3534 extremas[n] = src[2];
3535 return n + 1;
3536}
3537
3538static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3539 SkScalar ts[4];
3540 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3541 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3542 SkASSERT(n >= 0 && n <= 4);
3543 for (int i = 0; i < n; ++i) {
3544 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3545 }
3546 extremas[n] = src[3];
3547 return n + 1;
3548}
3549
Mike Reed8d3196b2017-02-03 11:34:13 -05003550SkRect SkPath::computeTightBounds() const {
3551 if (0 == this->countVerbs()) {
3552 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003553 }
3554
Mike Reed8d3196b2017-02-03 11:34:13 -05003555 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3556 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003557 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003558
Mike Reed0d7dac82017-02-02 17:45:56 -08003559 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3560 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003561 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003562
3563 // initial with the first MoveTo, so we don't have to check inside the switch
3564 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003565 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003566 for (;;) {
3567 int count = 0;
3568 switch (iter.next(pts)) {
3569 case SkPath::kMove_Verb:
3570 extremas[0] = pts[0];
3571 count = 1;
3572 break;
3573 case SkPath::kLine_Verb:
3574 extremas[0] = pts[1];
3575 count = 1;
3576 break;
3577 case SkPath::kQuad_Verb:
3578 count = compute_quad_extremas(pts, extremas);
3579 break;
3580 case SkPath::kConic_Verb:
3581 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3582 break;
3583 case SkPath::kCubic_Verb:
3584 count = compute_cubic_extremas(pts, extremas);
3585 break;
3586 case SkPath::kClose_Verb:
3587 break;
3588 case SkPath::kDone_Verb:
3589 goto DONE;
3590 }
3591 for (int i = 0; i < count; ++i) {
3592 Sk2s tmp = from_point(extremas[i]);
3593 min = Sk2s::Min(min, tmp);
3594 max = Sk2s::Max(max, tmp);
3595 }
3596 }
3597DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003598 SkRect bounds;
3599 min.store((SkPoint*)&bounds.fLeft);
3600 max.store((SkPoint*)&bounds.fRight);
3601 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003602}