blob: 605454bcf79f80317821a0e6bf527ae78c2aa5af [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
Hal Canary2a2f6752018-06-11 21:44:01 -04008#include "SkPath.h"
9
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080011#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040012#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080013#include "SkGeometry.h"
Hal Canary22be4c42018-06-12 12:37:31 -040014#include "SkMacros.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040016#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070017#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000018#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050019#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000020#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050021#include "SkSafeMath.h"
Hal Canary2a2f6752018-06-11 21:44:01 -040022#include "SkTo.h"
23
24#include <cmath>
reed@android.com8a1c16f2008-12-17 15:59:43 +000025
Mike Reeda99b6ce2017-02-04 11:04:26 -050026static float poly_eval(float A, float B, float C, float t) {
27 return (A * t + B) * t + C;
28}
29
30static float poly_eval(float A, float B, float C, float D, float t) {
31 return ((A * t + B) * t + C) * t + D;
32}
33
reed@android.com8a1c16f2008-12-17 15:59:43 +000034////////////////////////////////////////////////////////////////////////////
35
reed@google.com3563c9e2011-11-14 19:34:57 +000036/**
37 * Path.bounds is defined to be the bounds of all the control points.
38 * If we called bounds.join(r) we would skip r if r was empty, which breaks
39 * our promise. Hence we have a custom joiner that doesn't look at emptiness
40 */
41static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
42 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
43 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
44 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
45 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
46}
47
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000048static bool is_degenerate(const SkPath& path) {
49 SkPath::Iter iter(path, false);
50 SkPoint pts[4];
51 return SkPath::kDone_Verb == iter.next(pts);
52}
53
bsalomon@google.com30c174b2012-11-13 14:36:42 +000054class SkAutoDisableDirectionCheck {
55public:
56 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070057 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000058 }
59
60 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070061 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062 }
63
64private:
reed026beb52015-06-10 14:23:15 -070065 SkPath* fPath;
66 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000067};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000068#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000069
reed@android.com8a1c16f2008-12-17 15:59:43 +000070/* This guy's constructor/destructor bracket a path editing operation. It is
71 used when we know the bounds of the amount we are going to add to the path
72 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000073
reed@android.com8a1c16f2008-12-17 15:59:43 +000074 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000075 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000076 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000077
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000078 It also notes if the path was originally degenerate, and if so, sets
79 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000080 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000081 */
82class SkAutoPathBoundsUpdate {
83public:
84 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
85 this->init(path);
86 }
87
88 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
89 SkScalar right, SkScalar bottom) {
90 fRect.set(left, top, right, bottom);
91 this->init(path);
92 }
reed@google.comabf15c12011-01-18 20:35:51 +000093
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000095 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
96 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050097 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000098 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 }
100 }
reed@google.comabf15c12011-01-18 20:35:51 +0000101
reed@android.com8a1c16f2008-12-17 15:59:43 +0000102private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000103 SkPath* fPath;
104 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000106 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000107 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000108
reed@android.com6b82d1a2009-06-03 02:35:01 +0000109 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 // Cannot use fRect for our bounds unless we know it is sorted
111 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000113 // Mark the path's bounds as dirty if (1) they are, or (2) the path
114 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000115 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000117 if (fHasValidBounds && !fEmpty) {
118 joinNoEmptyChecks(&fRect, fPath->getBounds());
119 }
120 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 }
122};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000123#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000124
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125////////////////////////////////////////////////////////////////////////////
126
127/*
128 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000129 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000130 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
131
132 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000133 1. If we encounter degenerate segments, remove them
134 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
135 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
136 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000137*/
138
139////////////////////////////////////////////////////////////////////////////
140
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000141// flag to require a moveTo if we begin with something else, like lineTo etc.
142#define INITIAL_LASTMOVETOINDEX_VALUE ~0
143
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000144SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800145 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000146 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700147 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400148 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149}
150
151void SkPath::resetFields() {
152 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000153 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000154 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000155 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700156 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000157
158 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700159 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000160}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161
bungeman@google.coma5809a32013-06-21 15:13:34 +0000162SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000163 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164 this->copyFields(that);
165 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166}
167
168SkPath::~SkPath() {
169 SkDEBUGCODE(this->validate();)
170}
171
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172SkPath& SkPath::operator=(const SkPath& that) {
173 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175 if (this != &that) {
176 fPathRef.reset(SkRef(that.fPathRef.get()));
177 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000178 }
179 SkDEBUGCODE(this->validate();)
180 return *this;
181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183void SkPath::copyFields(const SkPath& that) {
184 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000185 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700187 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400188 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400189
190 // Non-atomic assignment of atomic values.
191 fConvexity .store(that.fConvexity .load());
192 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193}
194
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000195bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000196 // note: don't need to look at isConvex or bounds, since just comparing the
197 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000198 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000199 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000200}
201
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000203 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700204 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000205 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000206 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700207 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400208
209 // Non-atomic swaps of atomic values.
210 Convexity c = fConvexity.load();
211 fConvexity.store(that.fConvexity.load());
212 that.fConvexity.store(c);
213
214 uint8_t fd = fFirstDirection.load();
215 fFirstDirection.store(that.fFirstDirection.load());
216 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 }
218}
219
caryclark8e7b19d2016-02-18 04:11:48 -0800220bool SkPath::isInterpolatable(const SkPath& compare) const {
221 int count = fPathRef->countVerbs();
222 if (count != compare.fPathRef->countVerbs()) {
223 return false;
224 }
225 if (!count) {
226 return true;
227 }
228 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
229 count)) {
230 return false;
231 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800232 return !fPathRef->countWeights() ||
233 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800234 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
235}
236
237bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500238 int pointCount = fPathRef->countPoints();
239 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800240 return false;
241 }
Cary Clark7487ec82018-03-06 09:30:46 -0500242 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800243 return true;
244 }
caryclark8e7b19d2016-02-18 04:11:48 -0800245 out->reset();
246 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700247 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800248 return true;
249}
250
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000251static inline bool check_edge_against_rect(const SkPoint& p0,
252 const SkPoint& p1,
253 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700254 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000255 const SkPoint* edgeBegin;
256 SkVector v;
reed026beb52015-06-10 14:23:15 -0700257 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 v = p1 - p0;
259 edgeBegin = &p0;
260 } else {
261 v = p0 - p1;
262 edgeBegin = &p1;
263 }
264 if (v.fX || v.fY) {
265 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500266 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
267 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
268 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
269 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000270 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
271 return false;
272 }
273 }
274 return true;
275}
276
277bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
278 // This only handles non-degenerate convex paths currently.
279 if (kConvex_Convexity != this->getConvexity()) {
280 return false;
281 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000282
reed026beb52015-06-10 14:23:15 -0700283 SkPathPriv::FirstDirection direction;
284 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000285 return false;
286 }
287
288 SkPoint firstPt;
289 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500290 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 SkPath::Verb verb;
292 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400293 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000295 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000296
Lee Salzmana19f0242017-01-12 13:06:21 -0500297 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000298 int nextPt = -1;
299 switch (verb) {
300 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000301 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000302 SkDEBUGCODE(++moveCnt);
303 firstPt = prevPt = pts[0];
304 break;
305 case kLine_Verb:
306 nextPt = 1;
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 break;
310 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000311 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 2;
315 break;
316 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400318 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000319 nextPt = 3;
320 break;
321 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000322 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000323 break;
324 default:
325 SkDEBUGFAIL("unknown verb");
326 }
327 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800328 if (SkPath::kConic_Verb == verb) {
329 SkConic orig;
330 orig.set(pts, iter.conicWeight());
331 SkPoint quadPts[5];
332 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800333 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800334
335 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
336 return false;
337 }
338 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
339 return false;
340 }
341 } else {
342 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
343 return false;
344 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000345 }
346 prevPt = pts[nextPt];
347 }
348 }
349
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400350 if (segmentCount) {
351 return check_edge_against_rect(prevPt, firstPt, rect, direction);
352 }
353 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000354}
355
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000356uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000357 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800358#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400359 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
360 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000361#endif
362 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000363}
djsollen@google.come63793a2012-03-21 15:39:03 +0000364
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365void SkPath::reset() {
366 SkDEBUGCODE(this->validate();)
367
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000368 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000369 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370}
371
372void SkPath::rewind() {
373 SkDEBUGCODE(this->validate();)
374
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000375 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000376 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000377}
378
fsb1475b02016-01-20 09:51:07 -0800379bool SkPath::isLastContourClosed() const {
380 int verbCount = fPathRef->countVerbs();
381 if (0 == verbCount) {
382 return false;
383 }
384 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
385}
386
reed@google.com7e6c4d12012-05-10 14:05:43 +0000387bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000388 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000389
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000390 if (2 == verbCount) {
391 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
392 if (kLine_Verb == fPathRef->atVerb(1)) {
393 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000394 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000395 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000396 line[0] = pts[0];
397 line[1] = pts[1];
398 }
399 return true;
400 }
401 }
402 return false;
403}
404
caryclark@google.comf1316942011-07-26 19:54:45 +0000405/*
406 Determines if path is a rect by keeping track of changes in direction
407 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000408
caryclark@google.comf1316942011-07-26 19:54:45 +0000409 The direction is computed such that:
410 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000411 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000412 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000413 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000414
caryclark@google.comf1316942011-07-26 19:54:45 +0000415A rectangle cycles up/right/down/left or up/left/down/right.
416
417The test fails if:
418 The path is closed, and followed by a line.
419 A second move creates a new endpoint.
420 A diagonal line is parsed.
421 There's more than four changes of direction.
422 There's a discontinuity on the line (e.g., a move in the middle)
423 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000424 The path contains a quadratic or cubic.
425 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000426 *The rectangle doesn't complete a cycle.
427 *The final point isn't equal to the first point.
428
429 *These last two conditions we relax if we have a 3-edge path that would
430 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000431
caryclark@google.comf1316942011-07-26 19:54:45 +0000432It's OK if the path has:
433 Several colinear line segments composing a rectangle side.
434 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000435
caryclark@google.comf1316942011-07-26 19:54:45 +0000436The direction takes advantage of the corners found since opposite sides
437must travel in opposite directions.
438
439FIXME: Allow colinear quads and cubics to be treated like lines.
440FIXME: If the API passes fill-only, return true if the filled stroke
441 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000442
Cary Clark48c464a2018-04-16 12:06:07 -0400443 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000444 0x1 is set if the segment is horizontal
445 0x2 is set if the segment is moving to the right or down
446 thus:
447 two directions are opposites iff (dirA ^ dirB) == 0x2
448 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000449
caryclark@google.comf1316942011-07-26 19:54:45 +0000450 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000451static int rect_make_dir(SkScalar dx, SkScalar dy) {
452 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
453}
Cary Clark88ba9712018-04-12 14:00:24 -0400454
caryclark@google.comf68154a2012-11-21 15:18:06 +0000455bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400456 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000457 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400458 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400459 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400460 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
461 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400462 SkPoint firstCorner;
463 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000464 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400465 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400466 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400467 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000468 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700470 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000471 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000472 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700473 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
474 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000475 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000476 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700478 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000479 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400480 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400481 lastPt = pts;
482 }
Cary Clark88ba9712018-04-12 14:00:24 -0400483 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
484 SkVector lineDelta = lineEnd - lineStart;
485 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000486 return false; // diagonal
487 }
Cary Clark1eece782018-04-20 10:14:41 -0400488 if (!lineDelta.isFinite()) {
489 return false; // path contains infinity or NaN
490 }
Cary Clark88ba9712018-04-12 14:00:24 -0400491 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 break; // single point on side OK
493 }
Cary Clark48c464a2018-04-16 12:06:07 -0400494 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000495 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400496 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000497 corners = 1;
498 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400499 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000500 break;
501 }
502 if (closedOrMoved) {
503 return false; // closed followed by a line
504 }
Cary Clark48c464a2018-04-16 12:06:07 -0400505 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000506 break; // colinear with first
507 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400509 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400510 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400511 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400512 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400513 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 break; // colinear segment
515 }
Cary Clark48c464a2018-04-16 12:06:07 -0400516 directions[corners++] = nextDirection;
517 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400518 switch (corners) {
519 case 2:
520 firstCorner = lineStart;
521 break;
522 case 3:
523 if ((directions[0] ^ directions[2]) != 2) {
524 return false;
525 }
526 thirdCorner = lineEnd;
527 break;
528 case 4:
529 if ((directions[1] ^ directions[3]) != 2) {
530 return false;
531 }
532 break;
533 default:
534 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000535 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400536 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000537 break;
538 }
539 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000540 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000541 case kCubic_Verb:
542 return false; // quadratic, cubic not allowed
543 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400544 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700545 insertClose = true;
546 *currVerb -= 1; // try move again afterwards
547 goto addMissingClose;
548 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400549 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400550 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400551 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400552 closeXY = *firstPt - *lastPt;
553 if (closeXY.fX && closeXY.fY) {
554 return false; // we're diagonal, abort
555 }
Cary Clark8540e112018-04-11 14:30:27 -0400556 }
Cary Clark88ba9712018-04-12 14:00:24 -0400557 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000558 closedOrMoved = true;
559 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000560 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000561 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000562 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000563 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000564 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700565addMissingClose:
566 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000567 }
568 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400569 if (corners < 3 || corners > 4) {
570 return false;
571 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000572 if (savePts) {
573 *ptsPtr = savePts;
574 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400575 // check if close generates diagonal
576 closeXY = *firstPt - *lastPt;
577 if (closeXY.fX && closeXY.fY) {
578 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400579 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400580 if (rect) {
581 rect->set(firstCorner, thirdCorner);
582 }
583 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000584 *isClosed = autoClose;
585 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400586 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400587 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000588 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400589 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000590}
591
robertphillips4f662e62014-12-29 14:06:51 -0800592bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000593 SkDEBUGCODE(this->validate();)
594 int currVerb = 0;
595 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400596 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000597}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000598
caryclark95bc5f32015-04-08 08:34:15 -0700599bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000600 SkDEBUGCODE(this->validate();)
601 int currVerb = 0;
602 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000603 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400604 SkRect testRects[2];
605 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000606 return false;
607 }
Cary Clark5c715182018-04-09 16:07:11 -0400608 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 if (testRects[0].contains(testRects[1])) {
610 if (rects) {
611 rects[0] = testRects[0];
612 rects[1] = testRects[1];
613 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000614 if (dirs) {
615 dirs[0] = testDirs[0];
616 dirs[1] = testDirs[1];
617 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000618 return true;
619 }
620 if (testRects[1].contains(testRects[0])) {
621 if (rects) {
622 rects[0] = testRects[1];
623 rects[1] = testRects[0];
624 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000625 if (dirs) {
626 dirs[0] = testDirs[1];
627 dirs[1] = testDirs[0];
628 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000629 return true;
630 }
631 }
632 return false;
633}
634
Mike Reed0c3137c2018-02-20 13:57:05 -0500635bool SkPath::isOval(SkRect* bounds) const {
636 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
637}
638
639bool SkPath::isRRect(SkRRect* rrect) const {
640 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
641}
642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643int SkPath::countPoints() const {
644 return fPathRef->countPoints();
645}
646
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000647int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 SkDEBUGCODE(this->validate();)
649
650 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000651 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000652 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800653 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655}
656
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000657SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
659 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000660 }
661 return SkPoint::Make(0, 0);
662}
663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664int SkPath::countVerbs() const {
665 return fPathRef->countVerbs();
666}
667
668static inline void copy_verbs_reverse(uint8_t* inorderDst,
669 const uint8_t* reversedSrc,
670 int count) {
671 for (int i = 0; i < count; ++i) {
672 inorderDst[i] = reversedSrc[~i];
673 }
674}
675
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000676int SkPath::getVerbs(uint8_t dst[], int max) const {
677 SkDEBUGCODE(this->validate();)
678
679 SkASSERT(max >= 0);
680 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = SkMin32(max, fPathRef->countVerbs());
682 copy_verbs_reverse(dst, fPathRef->verbs(), count);
683 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000684}
685
reed@google.com294dd7b2011-10-11 11:58:32 +0000686bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 SkDEBUGCODE(this->validate();)
688
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000690 if (count > 0) {
691 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000694 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 if (lastPt) {
697 lastPt->set(0, 0);
698 }
699 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
caryclarkaec25102015-04-29 08:28:30 -0700702void SkPath::setPt(int index, SkScalar x, SkScalar y) {
703 SkDEBUGCODE(this->validate();)
704
705 int count = fPathRef->countPoints();
706 if (count <= index) {
707 return;
708 } else {
709 SkPathRef::Editor ed(&fPathRef);
710 ed.atPoint(index)->set(x, y);
711 }
712}
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714void SkPath::setLastPt(SkScalar x, SkScalar y) {
715 SkDEBUGCODE(this->validate();)
716
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 if (count == 0) {
719 this->moveTo(x, y);
720 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 SkPathRef::Editor ed(&fPathRef);
722 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 }
724}
725
reed@google.com04863fa2011-05-15 04:08:24 +0000726void SkPath::setConvexity(Convexity c) {
727 if (fConvexity != c) {
728 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000729 }
730}
731
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732//////////////////////////////////////////////////////////////////////////////
733// Construction methods
734
reed026beb52015-06-10 14:23:15 -0700735#define DIRTY_AFTER_EDIT \
736 do { \
737 fConvexity = kUnknown_Convexity; \
738 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000739 } while (0)
740
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741void SkPath::incReserve(U16CPU inc) {
742 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000743 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000744 SkDEBUGCODE(this->validate();)
745}
746
747void SkPath::moveTo(SkScalar x, SkScalar y) {
748 SkDEBUGCODE(this->validate();)
749
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000752 // remember our index
753 fLastMoveToIndex = fPathRef->countPoints();
754
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700756
757 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758}
759
760void SkPath::rMoveTo(SkScalar x, SkScalar y) {
761 SkPoint pt;
762 this->getLastPt(&pt);
763 this->moveTo(pt.fX + x, pt.fY + y);
764}
765
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000766void SkPath::injectMoveToIfNeeded() {
767 if (fLastMoveToIndex < 0) {
768 SkScalar x, y;
769 if (fPathRef->countVerbs() == 0) {
770 x = y = 0;
771 } else {
772 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
773 x = pt.fX;
774 y = pt.fY;
775 }
776 this->moveTo(x, y);
777 }
778}
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780void SkPath::lineTo(SkScalar x, SkScalar y) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787
reed@google.comb54455e2011-05-16 14:16:04 +0000788 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789}
790
791void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000792 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 SkPoint pt;
794 this->getLastPt(&pt);
795 this->lineTo(pt.fX + x, pt.fY + y);
796}
797
798void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
799 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000800
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000801 this->injectMoveToIfNeeded();
802
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000803 SkPathRef::Editor ed(&fPathRef);
804 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 pts[0].set(x1, y1);
806 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000807
reed@google.comb54455e2011-05-16 14:16:04 +0000808 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809}
810
811void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000812 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 SkPoint pt;
814 this->getLastPt(&pt);
815 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
816}
817
reed@google.com277c3f82013-05-31 15:17:50 +0000818void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
819 SkScalar w) {
820 // check for <= 0 or NaN with this test
821 if (!(w > 0)) {
822 this->lineTo(x2, y2);
823 } else if (!SkScalarIsFinite(w)) {
824 this->lineTo(x1, y1);
825 this->lineTo(x2, y2);
826 } else if (SK_Scalar1 == w) {
827 this->quadTo(x1, y1, x2, y2);
828 } else {
829 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000830
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000831 this->injectMoveToIfNeeded();
832
reed@google.com277c3f82013-05-31 15:17:50 +0000833 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000834 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000835 pts[0].set(x1, y1);
836 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000837
reed@google.com277c3f82013-05-31 15:17:50 +0000838 DIRTY_AFTER_EDIT;
839 }
840}
841
842void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
843 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000844 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000845 SkPoint pt;
846 this->getLastPt(&pt);
847 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
848}
849
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
851 SkScalar x3, SkScalar y3) {
852 SkDEBUGCODE(this->validate();)
853
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000854 this->injectMoveToIfNeeded();
855
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 SkPathRef::Editor ed(&fPathRef);
857 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 pts[0].set(x1, y1);
859 pts[1].set(x2, y2);
860 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861
reed@google.comb54455e2011-05-16 14:16:04 +0000862 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863}
864
865void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
866 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000867 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkPoint pt;
869 this->getLastPt(&pt);
870 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
871 pt.fX + x3, pt.fY + y3);
872}
873
874void SkPath::close() {
875 SkDEBUGCODE(this->validate();)
876
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 case kLine_Verb:
881 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000882 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 case kMove_Verb: {
885 SkPathRef::Editor ed(&fPathRef);
886 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 }
reed@google.com277c3f82013-05-31 15:17:50 +0000889 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000890 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000891 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000892 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000893 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000894 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000895 }
896 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000897
898 // signal that we need a moveTo to follow us (unless we're done)
899#if 0
900 if (fLastMoveToIndex >= 0) {
901 fLastMoveToIndex = ~fLastMoveToIndex;
902 }
903#else
904 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
905#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906}
907
908///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000909
fmalitac08d53e2015-11-17 09:53:29 -0800910namespace {
911
912template <unsigned N>
913class PointIterator {
914public:
915 PointIterator(SkPath::Direction dir, unsigned startIndex)
916 : fCurrent(startIndex % N)
917 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
918
919 const SkPoint& current() const {
920 SkASSERT(fCurrent < N);
921 return fPts[fCurrent];
922 }
923
924 const SkPoint& next() {
925 fCurrent = (fCurrent + fAdvance) % N;
926 return this->current();
927 }
928
929protected:
930 SkPoint fPts[N];
931
932private:
933 unsigned fCurrent;
934 unsigned fAdvance;
935};
936
937class RectPointIterator : public PointIterator<4> {
938public:
939 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
940 : PointIterator(dir, startIndex) {
941
942 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
943 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
944 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
945 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
946 }
947};
948
949class OvalPointIterator : public PointIterator<4> {
950public:
951 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
952 : PointIterator(dir, startIndex) {
953
954 const SkScalar cx = oval.centerX();
955 const SkScalar cy = oval.centerY();
956
957 fPts[0] = SkPoint::Make(cx, oval.fTop);
958 fPts[1] = SkPoint::Make(oval.fRight, cy);
959 fPts[2] = SkPoint::Make(cx, oval.fBottom);
960 fPts[3] = SkPoint::Make(oval.fLeft, cy);
961 }
962};
963
964class RRectPointIterator : public PointIterator<8> {
965public:
966 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
967 : PointIterator(dir, startIndex) {
968
969 const SkRect& bounds = rrect.getBounds();
970 const SkScalar L = bounds.fLeft;
971 const SkScalar T = bounds.fTop;
972 const SkScalar R = bounds.fRight;
973 const SkScalar B = bounds.fBottom;
974
975 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
976 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
977 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
978 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
979 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
980 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
981 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
982 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
983 }
984};
985
986} // anonymous namespace
987
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000988static void assert_known_direction(int dir) {
989 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
990}
991
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800993 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994}
995
996void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
997 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800998 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
999}
1000
1001void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001002 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001003 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001004 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001005 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001006 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001007
fmalitac08d53e2015-11-17 09:53:29 -08001008 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009
fmalitac08d53e2015-11-17 09:53:29 -08001010 const int kVerbs = 5; // moveTo + 3x lineTo + close
1011 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012
fmalitac08d53e2015-11-17 09:53:29 -08001013 RectPointIterator iter(rect, dir, startIndex);
1014
1015 this->moveTo(iter.current());
1016 this->lineTo(iter.next());
1017 this->lineTo(iter.next());
1018 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001020
1021 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022}
1023
reed@google.com744faba2012-05-29 19:54:52 +00001024void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1025 SkDEBUGCODE(this->validate();)
1026 if (count <= 0) {
1027 return;
1028 }
1029
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001030 fLastMoveToIndex = fPathRef->countPoints();
1031
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032 // +close makes room for the extra kClose_Verb
1033 SkPathRef::Editor ed(&fPathRef, count+close, count);
1034
1035 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001036 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001037 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1038 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001039 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001040
reed@google.com744faba2012-05-29 19:54:52 +00001041 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001042 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001043 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001044 }
1045
reed@google.com744faba2012-05-29 19:54:52 +00001046 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001047 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001048}
1049
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050#include "SkGeometry.h"
1051
reedf90ea012015-01-29 12:03:58 -08001052static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1053 SkPoint* pt) {
1054 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055 // Chrome uses this path to move into and out of ovals. If not
1056 // treated as a special case the moves can distort the oval's
1057 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001058 pt->set(oval.fRight, oval.centerY());
1059 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060 } else if (0 == oval.width() && 0 == oval.height()) {
1061 // Chrome will sometimes create 0 radius round rects. Having degenerate
1062 // quad segments in the path prevents the path from being recognized as
1063 // a rect.
1064 // TODO: optimizing the case where only one of width or height is zero
1065 // should also be considered. This case, however, doesn't seem to be
1066 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001067 pt->set(oval.fRight, oval.fTop);
1068 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001069 }
reedf90ea012015-01-29 12:03:58 -08001070 return false;
1071}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072
reedd5d27d92015-02-09 13:54:43 -08001073// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1074//
1075static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1076 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1077 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1078 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079
1080 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001081 loss in radians-conversion and/or sin/cos, we may end up with coincident
1082 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1083 of drawing a nearly complete circle (good).
1084 e.g. canvas.drawArc(0, 359.99, ...)
1085 -vs- canvas.drawArc(0, 359.9, ...)
1086 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001087 */
reedd5d27d92015-02-09 13:54:43 -08001088 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 SkScalar sw = SkScalarAbs(sweepAngle);
1090 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1091 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1092 // make a guess at a tiny angle (in radians) to tweak by
1093 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1094 // not sure how much will be enough, so we use a loop
1095 do {
1096 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001097 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1098 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099 }
1100 }
reedd5d27d92015-02-09 13:54:43 -08001101 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1102}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001103
reed9e779d42015-02-17 11:43:14 -08001104/**
1105 * If this returns 0, then the caller should just line-to the singlePt, else it should
1106 * ignore singlePt and append the specified number of conics.
1107 */
reedd5d27d92015-02-09 13:54:43 -08001108static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001109 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1110 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001111 SkMatrix matrix;
1112
1113 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1114 matrix.postTranslate(oval.centerX(), oval.centerY());
1115
reed9e779d42015-02-17 11:43:14 -08001116 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1117 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001118 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001119 }
1120 return count;
reedd5d27d92015-02-09 13:54:43 -08001121}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001122
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001123void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001125 SkRRect rrect;
1126 rrect.setRectRadii(rect, (const SkVector*) radii);
1127 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128}
1129
reed@google.com4ed0fb72012-12-12 20:48:18 +00001130void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001131 // legacy start indices: 6 (CW) and 7(CCW)
1132 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1133}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001134
fmalitac08d53e2015-11-17 09:53:29 -08001135void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1136 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001137
caryclarkda707bf2015-11-19 14:47:43 -08001138 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001139 const SkRect& bounds = rrect.getBounds();
1140
Brian Salomon0a241ce2017-12-15 11:31:05 -05001141 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001142 // degenerate(rect) => radii points are collapsing
1143 this->addRect(bounds, dir, (startIndex + 1) / 2);
1144 } else if (rrect.isOval()) {
1145 // degenerate(oval) => line points are collapsing
1146 this->addOval(bounds, dir, startIndex / 2);
1147 } else {
1148 fFirstDirection = this->hasOnlyMoveTos() ?
1149 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1150
1151 SkAutoPathBoundsUpdate apbu(this, bounds);
1152 SkAutoDisableDirectionCheck addc(this);
1153
1154 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1155 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1156 const SkScalar weight = SK_ScalarRoot2Over2;
1157
1158 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1159 const int kVerbs = startsWithConic
1160 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1161 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1162 this->incReserve(kVerbs);
1163
1164 RRectPointIterator rrectIter(rrect, dir, startIndex);
1165 // Corner iterator indices follow the collapsed radii model,
1166 // adjusted such that the start pt is "behind" the radii start pt.
1167 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1168 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1169
1170 this->moveTo(rrectIter.current());
1171 if (startsWithConic) {
1172 for (unsigned i = 0; i < 3; ++i) {
1173 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1174 this->lineTo(rrectIter.next());
1175 }
1176 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1177 // final lineTo handled by close().
1178 } else {
1179 for (unsigned i = 0; i < 4; ++i) {
1180 this->lineTo(rrectIter.next());
1181 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1182 }
1183 }
1184 this->close();
1185
caryclarkda707bf2015-11-19 14:47:43 -08001186 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001187 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001188
fmalitac08d53e2015-11-17 09:53:29 -08001189 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1190 }
1191
1192 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001193}
1194
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001195bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001196 int count = fPathRef->countVerbs();
1197 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1198 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001199 if (*verbs == kLine_Verb ||
1200 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001201 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001202 *verbs == kCubic_Verb) {
1203 return false;
1204 }
1205 ++verbs;
1206 }
1207 return true;
1208}
1209
Brian Osmana2318572017-07-10 15:09:26 -04001210bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1211 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001212 if (count < 2) {
1213 return true;
1214 }
Brian Osmana2318572017-07-10 15:09:26 -04001215 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001216 const SkPoint& first = *pts;
1217 for (int index = 1; index < count; ++index) {
1218 if (first != pts[index]) {
1219 return false;
1220 }
1221 }
1222 return true;
1223}
1224
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001225void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1226 Direction dir) {
1227 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001228
humper@google.com75e3ca12013-04-08 21:44:11 +00001229 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001230 return;
1231 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001232
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001233 SkRRect rrect;
1234 rrect.setRectXY(rect, rx, ry);
1235 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001236}
1237
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001239 // legacy start index: 1
1240 this->addOval(oval, dir, 1);
1241}
1242
1243void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001244 assert_known_direction(dir);
1245
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001246 /* If addOval() is called after previous moveTo(),
1247 this path is still marked as an oval. This is used to
1248 fit into WebKit's calling sequences.
1249 We can't simply check isEmpty() in this case, as additional
1250 moveTo() would mark the path non empty.
1251 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001252 bool isOval = hasOnlyMoveTos();
1253 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001254 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001255 } else {
reed026beb52015-06-10 14:23:15 -07001256 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001257 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001258
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001259 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001260 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261
fmalitac08d53e2015-11-17 09:53:29 -08001262 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1263 const int kVerbs = 6; // moveTo + 4x conicTo + close
1264 this->incReserve(kVerbs);
1265
1266 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1267 // The corner iterator pts are tracking "behind" the oval/radii pts.
1268 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001269 const SkScalar weight = SK_ScalarRoot2Over2;
1270
fmalitac08d53e2015-11-17 09:53:29 -08001271 this->moveTo(ovalIter.current());
1272 for (unsigned i = 0; i < 4; ++i) {
1273 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001274 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276
fmalitac08d53e2015-11-17 09:53:29 -08001277 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1278
robertphillips@google.com466310d2013-12-03 16:43:54 +00001279 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001280
bsalomon78d58d12016-05-27 09:17:04 -07001281 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001282}
1283
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1285 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001286 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 }
1288}
1289
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1291 bool forceMoveTo) {
1292 if (oval.width() < 0 || oval.height() < 0) {
1293 return;
1294 }
1295
reedf90ea012015-01-29 12:03:58 -08001296 if (fPathRef->countVerbs() == 0) {
1297 forceMoveTo = true;
1298 }
1299
1300 SkPoint lonePt;
1301 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1302 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1303 return;
1304 }
1305
reedd5d27d92015-02-09 13:54:43 -08001306 SkVector startV, stopV;
1307 SkRotationDirection dir;
1308 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1309
reed9e779d42015-02-17 11:43:14 -08001310 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001311
Brian Salomone4949402018-04-26 15:22:04 -04001312 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1313 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1314 // arcs from the same oval.
1315 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1316 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001317 if (forceMoveTo) {
1318 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001319 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001320 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1321 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1322 this->lineTo(pt);
1323 }
1324 };
1325
xidachen6069dda2016-10-06 05:42:23 -07001326 // At this point, we know that the arc is not a lone point, but startV == stopV
1327 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1328 // cannot handle it.
1329 if (startV == stopV) {
1330 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1331 SkScalar radiusX = oval.width() / 2;
1332 SkScalar radiusY = oval.height() / 2;
1333 // We cannot use SkScalarSinCos function in the next line because
1334 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1335 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001336 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001337 // make sin(endAngle) to be 0 which will then draw a dot.
1338 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1339 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001340 addPt(singlePt);
xidachen6069dda2016-10-06 05:42:23 -07001341 return;
1342 }
1343
reedd5d27d92015-02-09 13:54:43 -08001344 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001345 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001346 if (count) {
1347 this->incReserve(count * 2 + 1);
1348 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001349 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001350 for (int i = 0; i < count; ++i) {
1351 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1352 }
reed9e779d42015-02-17 11:43:14 -08001353 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001354 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001355 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356}
1357
caryclark55d49052016-01-23 05:07:04 -08001358// This converts the SVG arc to conics.
1359// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1360// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1361// See also SVG implementation notes:
1362// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1363// Note that arcSweep bool value is flipped from the original implementation.
1364void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1365 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001366 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001367 SkPoint srcPts[2];
1368 this->getLastPt(&srcPts[0]);
1369 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1370 // joining the endpoints.
1371 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1372 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001373 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001374 return;
1375 }
1376 // If the current point and target point for the arc are identical, it should be treated as a
1377 // zero length path. This ensures continuity in animations.
1378 srcPts[1].set(x, y);
1379 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001380 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001381 return;
1382 }
1383 rx = SkScalarAbs(rx);
1384 ry = SkScalarAbs(ry);
1385 SkVector midPointDistance = srcPts[0] - srcPts[1];
1386 midPointDistance *= 0.5f;
1387
1388 SkMatrix pointTransform;
1389 pointTransform.setRotate(-angle);
1390
1391 SkPoint transformedMidPoint;
1392 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1393 SkScalar squareRx = rx * rx;
1394 SkScalar squareRy = ry * ry;
1395 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1396 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1397
1398 // Check if the radii are big enough to draw the arc, scale radii if not.
1399 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1400 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1401 if (radiiScale > 1) {
1402 radiiScale = SkScalarSqrt(radiiScale);
1403 rx *= radiiScale;
1404 ry *= radiiScale;
1405 }
1406
1407 pointTransform.setScale(1 / rx, 1 / ry);
1408 pointTransform.preRotate(-angle);
1409
1410 SkPoint unitPts[2];
1411 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1412 SkVector delta = unitPts[1] - unitPts[0];
1413
1414 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1415 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1416
1417 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1418 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1419 scaleFactor = -scaleFactor;
1420 }
1421 delta.scale(scaleFactor);
1422 SkPoint centerPoint = unitPts[0] + unitPts[1];
1423 centerPoint *= 0.5f;
1424 centerPoint.offset(-delta.fY, delta.fX);
1425 unitPts[0] -= centerPoint;
1426 unitPts[1] -= centerPoint;
1427 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1428 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1429 SkScalar thetaArc = theta2 - theta1;
1430 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1431 thetaArc += SK_ScalarPI * 2;
1432 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1433 thetaArc -= SK_ScalarPI * 2;
1434 }
1435 pointTransform.setRotate(angle);
1436 pointTransform.preScale(rx, ry);
1437
Cary Clark1acd3c72017-12-08 11:37:01 -05001438#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001439 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001440#else
1441 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1442 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1443#endif
caryclark55d49052016-01-23 05:07:04 -08001444 SkScalar thetaWidth = thetaArc / segments;
1445 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1446 if (!SkScalarIsFinite(t)) {
1447 return;
1448 }
1449 SkScalar startTheta = theta1;
1450 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001451#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1452 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1453 return scalar == SkScalarFloorToScalar(scalar);
1454 };
1455 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1456 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1457 scalar_is_integer(x) && scalar_is_integer(y);
1458#endif
caryclark55d49052016-01-23 05:07:04 -08001459 for (int i = 0; i < segments; ++i) {
1460 SkScalar endTheta = startTheta + thetaWidth;
1461 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1462
1463 unitPts[1].set(cosEndTheta, sinEndTheta);
1464 unitPts[1] += centerPoint;
1465 unitPts[0] = unitPts[1];
1466 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1467 SkPoint mapped[2];
1468 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001469 /*
1470 Computing the arc width introduces rounding errors that cause arcs to start
1471 outside their marks. A round rect may lose convexity as a result. If the input
1472 values are on integers, place the conic on integers as well.
1473 */
1474#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1475 if (expectIntegers) {
1476 SkScalar* mappedScalars = &mapped[0].fX;
1477 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1478 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1479 }
1480 }
1481#endif
caryclark55d49052016-01-23 05:07:04 -08001482 this->conicTo(mapped[0], mapped[1], w);
1483 startTheta = endTheta;
1484 }
1485}
1486
1487void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1488 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1489 SkPoint currentPoint;
1490 this->getLastPt(&currentPoint);
1491 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1492}
1493
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001494void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 if (oval.isEmpty() || 0 == sweepAngle) {
1496 return;
1497 }
1498
1499 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1500
1501 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001502 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1503 // See SkPath::addOval() docs.
1504 SkScalar startOver90 = startAngle / 90.f;
1505 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1506 SkScalar error = startOver90 - startOver90I;
1507 if (SkScalarNearlyEqual(error, 0)) {
1508 // Index 1 is at startAngle == 0.
1509 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1510 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1511 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1512 (unsigned) startIndex);
1513 return;
1514 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 }
bsalomon1978ce22016-05-31 10:42:16 -07001516 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517}
1518
1519/*
1520 Need to handle the case when the angle is sharp, and our computed end-points
1521 for the arc go behind pt1 and/or p2...
1522*/
reedc7789042015-01-29 12:59:11 -08001523void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001524 if (radius == 0) {
1525 this->lineTo(x1, y1);
1526 return;
1527 }
1528
1529 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001530
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 // need to know our prev pt so we can construct tangent vectors
1532 {
1533 SkPoint start;
1534 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001535 // Handle degenerate cases by adding a line to the first point and
1536 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 before.setNormalize(x1 - start.fX, y1 - start.fY);
1538 after.setNormalize(x2 - x1, y2 - y1);
1539 }
reed@google.comabf15c12011-01-18 20:35:51 +00001540
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 SkScalar cosh = SkPoint::DotProduct(before, after);
1542 SkScalar sinh = SkPoint::CrossProduct(before, after);
1543
1544 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001545 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 return;
1547 }
reed@google.comabf15c12011-01-18 20:35:51 +00001548
Mike Reeda99b6ce2017-02-04 11:04:26 -05001549 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550
Mike Reeda99b6ce2017-02-04 11:04:26 -05001551 SkScalar xx = x1 - dist * before.fX;
1552 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001553 after.setLength(dist);
1554 this->lineTo(xx, yy);
1555 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1556 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557}
1558
1559///////////////////////////////////////////////////////////////////////////////
1560
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001561void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 SkMatrix matrix;
1563
1564 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001565 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566}
1567
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001568void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001571 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 SkPoint pts[4];
1573 Verb verb;
1574
Cary Clark9480d822017-10-19 18:01:13 -04001575 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001576 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 while ((verb = iter.next(pts)) != kDone_Verb) {
1578 switch (verb) {
1579 case kMove_Verb:
1580 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001581 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1582 injectMoveToIfNeeded(); // In case last contour is closed
1583 this->lineTo(pts[0]);
1584 } else {
1585 this->moveTo(pts[0]);
1586 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 break;
1588 case kLine_Verb:
1589 proc(matrix, &pts[1], &pts[1], 1);
1590 this->lineTo(pts[1]);
1591 break;
1592 case kQuad_Verb:
1593 proc(matrix, &pts[1], &pts[1], 2);
1594 this->quadTo(pts[1], pts[2]);
1595 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001596 case kConic_Verb:
1597 proc(matrix, &pts[1], &pts[1], 2);
1598 this->conicTo(pts[1], pts[2], iter.conicWeight());
1599 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 case kCubic_Verb:
1601 proc(matrix, &pts[1], &pts[1], 3);
1602 this->cubicTo(pts[1], pts[2], pts[3]);
1603 break;
1604 case kClose_Verb:
1605 this->close();
1606 break;
1607 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001608 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001610 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 }
1612}
1613
1614///////////////////////////////////////////////////////////////////////////////
1615
reed@google.com277c3f82013-05-31 15:17:50 +00001616static int pts_in_verb(unsigned verb) {
1617 static const uint8_t gPtsInVerb[] = {
1618 1, // kMove
1619 1, // kLine
1620 2, // kQuad
1621 2, // kConic
1622 3, // kCubic
1623 0, // kClose
1624 0 // kDone
1625 };
1626
1627 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1628 return gPtsInVerb[verb];
1629}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631// ignore the last point of the 1st contour
1632void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001633 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1634 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 return;
1636 }
caryclark51c56782016-11-07 05:09:28 -08001637 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1638 SkASSERT(verbsEnd[0] == kMove_Verb);
1639 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1640 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641
caryclark51c56782016-11-07 05:09:28 -08001642 while (verbs < verbsEnd) {
1643 uint8_t v = *verbs++;
1644 pts -= pts_in_verb(v);
1645 switch (v) {
1646 case kMove_Verb:
1647 // if the path has multiple contours, stop after reversing the last
1648 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001650 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 break;
1652 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001653 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001655 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001656 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001657 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001659 this->cubicTo(pts[2], pts[1], pts[0]);
1660 break;
1661 case kClose_Verb:
1662 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 break;
1664 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001665 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 break;
1667 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 }
1669}
1670
reed@google.com63d73742012-01-10 15:33:12 +00001671void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001674 const SkPoint* pts = src.fPathRef->pointsEnd();
1675 // we will iterator through src's verbs backwards
1676 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1677 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001678 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001679
1680 bool needMove = true;
1681 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001682 while (verbs < verbsEnd) {
1683 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001684 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001685
1686 if (needMove) {
1687 --pts;
1688 this->moveTo(pts->fX, pts->fY);
1689 needMove = false;
1690 }
1691 pts -= n;
1692 switch (v) {
1693 case kMove_Verb:
1694 if (needClose) {
1695 this->close();
1696 needClose = false;
1697 }
1698 needMove = true;
1699 pts += 1; // so we see the point in "if (needMove)" above
1700 break;
1701 case kLine_Verb:
1702 this->lineTo(pts[0]);
1703 break;
1704 case kQuad_Verb:
1705 this->quadTo(pts[1], pts[0]);
1706 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001707 case kConic_Verb:
1708 this->conicTo(pts[1], pts[0], *--conicWeights);
1709 break;
reed@google.com63d73742012-01-10 15:33:12 +00001710 case kCubic_Verb:
1711 this->cubicTo(pts[2], pts[1], pts[0]);
1712 break;
1713 case kClose_Verb:
1714 needClose = true;
1715 break;
1716 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001717 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001718 }
1719 }
1720}
1721
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722///////////////////////////////////////////////////////////////////////////////
1723
1724void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1725 SkMatrix matrix;
1726
1727 matrix.setTranslate(dx, dy);
1728 this->transform(matrix, dst);
1729}
1730
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1732 int level = 2) {
1733 if (--level >= 0) {
1734 SkPoint tmp[7];
1735
1736 SkChopCubicAtHalf(pts, tmp);
1737 subdivide_cubic_to(path, &tmp[0], level);
1738 subdivide_cubic_to(path, &tmp[3], level);
1739 } else {
1740 path->cubicTo(pts[1], pts[2], pts[3]);
1741 }
1742}
1743
1744void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1745 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001746 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 dst = (SkPath*)this;
1748 }
1749
tomhudson@google.com8d430182011-06-06 19:11:19 +00001750 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 SkPath tmp;
1752 tmp.fFillType = fFillType;
1753
1754 SkPath::Iter iter(*this, false);
1755 SkPoint pts[4];
1756 SkPath::Verb verb;
1757
reed@google.com4a3b7142012-05-16 17:16:46 +00001758 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 switch (verb) {
1760 case kMove_Verb:
1761 tmp.moveTo(pts[0]);
1762 break;
1763 case kLine_Verb:
1764 tmp.lineTo(pts[1]);
1765 break;
1766 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001767 // promote the quad to a conic
1768 tmp.conicTo(pts[1], pts[2],
1769 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001771 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001772 tmp.conicTo(pts[1], pts[2],
1773 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001774 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 case kCubic_Verb:
1776 subdivide_cubic_to(&tmp, pts);
1777 break;
1778 case kClose_Verb:
1779 tmp.close();
1780 break;
1781 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001782 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 break;
1784 }
1785 }
1786
1787 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 SkPathRef::Editor ed(&dst->fPathRef);
1789 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001790 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1793
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001796 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001797 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001799
reed026beb52015-06-10 14:23:15 -07001800 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1801 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001802 } else {
1803 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001804 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1805 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001806 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001807 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1808 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001809 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001810 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001811 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001812 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001813 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001814 }
1815 }
1816
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 SkDEBUGCODE(dst->validate();)
1818 }
1819}
1820
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821///////////////////////////////////////////////////////////////////////////////
1822///////////////////////////////////////////////////////////////////////////////
1823
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001825 kEmptyContour_SegmentState, // The current contour is empty. We may be
1826 // starting processing or we may have just
1827 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1829 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1830 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831};
1832
1833SkPath::Iter::Iter() {
1834#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001835 fPts = nullptr;
1836 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001839 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840#endif
1841 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001842 fVerbs = nullptr;
1843 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 fNeedClose = false;
1845}
1846
1847SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1848 this->setPath(path, forceClose);
1849}
1850
1851void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001852 fPts = path.fPathRef->points();
1853 fVerbs = path.fPathRef->verbs();
1854 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001855 fConicWeights = path.fPathRef->conicWeights();
1856 if (fConicWeights) {
1857 fConicWeights -= 1; // begin one behind
1858 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001860 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 fForceClose = SkToU8(forceClose);
1862 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001863 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864}
1865
1866bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001867 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 return false;
1869 }
1870 if (fForceClose) {
1871 return true;
1872 }
1873
1874 const uint8_t* verbs = fVerbs;
1875 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001876
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001877 if (kMove_Verb == *(verbs - 1)) {
1878 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 }
1880
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 while (verbs > stop) {
1882 // verbs points one beyond the current verb, decrement first.
1883 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 if (kMove_Verb == v) {
1885 break;
1886 }
1887 if (kClose_Verb == v) {
1888 return true;
1889 }
1890 }
1891 return false;
1892}
1893
1894SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001895 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001897 // A special case: if both points are NaN, SkPoint::operation== returns
1898 // false, but the iterator expects that they are treated as the same.
1899 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001900 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1901 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001902 return kClose_Verb;
1903 }
1904
reed@google.com9e25dbf2012-05-15 17:05:38 +00001905 pts[0] = fLastPt;
1906 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 fLastPt = fMoveTo;
1908 fCloseLine = true;
1909 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001910 } else {
1911 pts[0] = fMoveTo;
1912 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914}
1915
reed@google.com9e25dbf2012-05-15 17:05:38 +00001916const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 if (fSegmentState == kAfterMove_SegmentState) {
1918 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001920 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1923 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926}
1927
caryclarke8c56662015-07-14 11:19:26 -07001928void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 // We need to step over anything that will not move the current draw point
1930 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001931 const uint8_t* lastMoveVerb = nullptr;
1932 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001933 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 SkPoint lastPt = fLastPt;
1935 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001936 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 switch (verb) {
1938 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 // Keep a record of this most recent move
1940 lastMoveVerb = fVerbs;
1941 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001942 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001943 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001944 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 fPts++;
1946 break;
1947
1948 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001949 // A close when we are in a segment is always valid except when it
1950 // follows a move which follows a segment.
1951 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 return;
1953 }
1954 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 break;
1957
1958 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001959 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 if (lastMoveVerb) {
1961 fVerbs = lastMoveVerb;
1962 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001963 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 return;
1965 }
1966 return;
1967 }
1968 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001969 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 fPts++;
1971 break;
1972
reed@google.com277c3f82013-05-31 15:17:50 +00001973 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001975 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 if (lastMoveVerb) {
1977 fVerbs = lastMoveVerb;
1978 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001979 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 return;
1981 }
1982 return;
1983 }
1984 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001985 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001986 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001987 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 break;
1989
1990 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001991 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 if (lastMoveVerb) {
1993 fVerbs = lastMoveVerb;
1994 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001995 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001996 return;
1997 }
1998 return;
1999 }
2000 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002001 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 fPts += 3;
2003 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002004
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002005 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002006 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002007 }
2008 }
2009}
2010
reed@google.com4a3b7142012-05-16 17:16:46 +00002011SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002012 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002013
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002015 // Close the curve if requested and if there is some curve to close
2016 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002017 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 return kLine_Verb;
2019 }
2020 fNeedClose = false;
2021 return kClose_Verb;
2022 }
2023 return kDone_Verb;
2024 }
2025
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002026 // fVerbs is one beyond the current verb, decrement first
2027 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002028 const SkPoint* SK_RESTRICT srcPts = fPts;
2029 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030
2031 switch (verb) {
2032 case kMove_Verb:
2033 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002034 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 verb = this->autoClose(pts);
2036 if (verb == kClose_Verb) {
2037 fNeedClose = false;
2038 }
2039 return (Verb)verb;
2040 }
2041 if (fVerbs == fVerbStop) { // might be a trailing moveto
2042 return kDone_Verb;
2043 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002044 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002045 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002047 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002048 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 fNeedClose = fForceClose;
2050 break;
2051 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002052 pts[0] = this->cons_moveTo();
2053 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 fLastPt = srcPts[0];
2055 fCloseLine = false;
2056 srcPts += 1;
2057 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002058 case kConic_Verb:
2059 fConicWeights += 1;
2060 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002062 pts[0] = this->cons_moveTo();
2063 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 fLastPt = srcPts[1];
2065 srcPts += 2;
2066 break;
2067 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002068 pts[0] = this->cons_moveTo();
2069 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 fLastPt = srcPts[2];
2071 srcPts += 3;
2072 break;
2073 case kClose_Verb:
2074 verb = this->autoClose(pts);
2075 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002076 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077 } else {
2078 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002079 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002081 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 break;
2083 }
2084 fPts = srcPts;
2085 return (Verb)verb;
2086}
2087
2088///////////////////////////////////////////////////////////////////////////////
2089
Ben Wagner4d1955c2017-03-10 13:08:15 -05002090#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002091#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002092#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002093
reed@google.com51bbe752013-01-17 22:07:50 +00002094static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002095 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002096 str->append(label);
2097 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002098
reed@google.com51bbe752013-01-17 22:07:50 +00002099 const SkScalar* values = &pts[0].fX;
2100 count *= 2;
2101
2102 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002103 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002104 if (i < count - 1) {
2105 str->append(", ");
2106 }
2107 }
Mike Reed405b9d22018-01-09 15:11:08 -05002108 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002109 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002110 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002111 }
caryclark08fa28c2014-10-23 13:08:56 -07002112 str->append(");");
reede05fed02014-12-15 07:59:53 -08002113 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002114 str->append(" // ");
2115 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002116 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002117 if (i < count - 1) {
2118 str->append(", ");
2119 }
2120 }
2121 if (conicWeight >= 0) {
2122 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002123 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002124 }
2125 }
2126 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002127}
2128
caryclarke9562592014-09-15 09:26:09 -07002129void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002130 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 Iter iter(*this, forceClose);
2132 SkPoint pts[4];
2133 Verb verb;
2134
reed@google.com51bbe752013-01-17 22:07:50 +00002135 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002136 char const * const gFillTypeStrs[] = {
2137 "Winding",
2138 "EvenOdd",
2139 "InverseWinding",
2140 "InverseEvenOdd",
2141 };
2142 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2143 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002144 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 switch (verb) {
2146 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002147 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 break;
2149 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002150 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 break;
2152 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002153 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002155 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002156 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002157 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002159 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 break;
2161 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002162 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
2164 default:
2165 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2166 verb = kDone_Verb; // stop the loop
2167 break;
2168 }
caryclark1049f122015-04-20 08:31:59 -07002169 if (!wStream && builder.size()) {
2170 SkDebugf("%s", builder.c_str());
2171 builder.reset();
2172 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 }
caryclark66a5d8b2014-06-24 08:30:15 -07002174 if (wStream) {
2175 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002176 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177}
2178
reed@android.come522ca52009-11-23 20:10:41 +00002179void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002180 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002181}
2182
2183void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002184 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002185}
2186
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002187
Cary Clark0461e9f2017-08-25 15:13:38 -04002188bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002189 if ((fFillType & ~3) != 0) {
2190 return false;
2191 }
reed@google.comabf15c12011-01-18 20:35:51 +00002192
djsollen@google.com077348c2012-10-22 20:23:32 +00002193#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002194 if (!fBoundsIsDirty) {
2195 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002196
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002197 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002198 if (SkToBool(fIsFinite) != isFinite) {
2199 return false;
2200 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002201
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002202 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002203 // if we're empty, fBounds may be empty but translated, so we can't
2204 // necessarily compare to bounds directly
2205 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2206 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002207 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2208 return false;
2209 }
reed@android.come522ca52009-11-23 20:10:41 +00002210 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002211 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002212 if (!fBounds.isEmpty()) {
2213 return false;
2214 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002215 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002216 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002217 if (!fBounds.contains(bounds)) {
2218 return false;
2219 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002220 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002221 }
reed@android.come522ca52009-11-23 20:10:41 +00002222 }
2223 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002224#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002225 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002226}
reed@android.come522ca52009-11-23 20:10:41 +00002227
reed@google.com04863fa2011-05-15 04:08:24 +00002228///////////////////////////////////////////////////////////////////////////////
2229
reed@google.com0b7b9822011-05-16 12:29:27 +00002230static int sign(SkScalar x) { return x < 0; }
2231#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002232
robertphillipsc506e302014-09-16 09:43:31 -07002233enum DirChange {
2234 kLeft_DirChange,
2235 kRight_DirChange,
2236 kStraight_DirChange,
2237 kBackwards_DirChange,
2238
2239 kInvalid_DirChange
2240};
2241
2242
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002243static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002244 // The error epsilon was empirically derived; worse case round rects
2245 // with a mid point outset by 2x float epsilon in tests had an error
2246 // of 12.
2247 const int epsilon = 16;
2248 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2249 return false;
2250 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002251 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002252 int aBits = SkFloatAs2sCompliment(compA);
2253 int bBits = SkFloatAs2sCompliment(compB);
2254 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002255}
2256
caryclarkb4216502015-03-02 13:02:34 -08002257static bool approximately_zero_when_compared_to(double x, double y) {
2258 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002259}
2260
caryclarkb4216502015-03-02 13:02:34 -08002261
reed@google.com04863fa2011-05-15 04:08:24 +00002262// only valid for a single contour
2263struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002264 Convexicator()
2265 : fPtCount(0)
2266 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002267 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002268 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002269 , fIsCurve(false)
2270 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002271 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002272 // warnings
djsollen2f124632016-04-29 13:53:05 -07002273 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002274 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002275 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002276 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002277 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002278
2279 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002280 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002281 }
2282
2283 SkPath::Convexity getConvexity() const { return fConvexity; }
2284
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002285 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002286 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002287
reed@google.com04863fa2011-05-15 04:08:24 +00002288 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002289 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002290 return;
2291 }
2292
2293 if (0 == fPtCount) {
2294 fCurrPt = pt;
2295 ++fPtCount;
2296 } else {
2297 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002298 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002299 if (!SkScalarIsFinite(lengthSqd)) {
2300 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002301 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002302 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002303 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002304 fCurrPt = pt;
2305 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002306 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002307 } else {
2308 SkASSERT(fPtCount > 2);
2309 this->addVec(vec);
2310 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002311
reed@google.com85b6e392011-05-15 20:25:17 +00002312 int sx = sign(vec.fX);
2313 int sy = sign(vec.fY);
2314 fDx += (sx != fSx);
2315 fDy += (sy != fSy);
2316 fSx = sx;
2317 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002318
reed@google.com85b6e392011-05-15 20:25:17 +00002319 if (fDx > 3 || fDy > 3) {
2320 fConvexity = SkPath::kConcave_Convexity;
2321 }
reed@google.com04863fa2011-05-15 04:08:24 +00002322 }
2323 }
2324 }
2325
2326 void close() {
2327 if (fPtCount > 2) {
2328 this->addVec(fFirstVec);
2329 }
2330 }
2331
caryclarkb4216502015-03-02 13:02:34 -08002332 DirChange directionChange(const SkVector& curVec) {
2333 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2334
2335 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2336 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2337 largest = SkTMax(largest, -smallest);
2338
2339 if (!almost_equal(largest, largest + cross)) {
2340 int sign = SkScalarSignAsInt(cross);
2341 if (sign) {
2342 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2343 }
2344 }
2345
2346 if (cross) {
2347 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2348 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2349 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2350 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2351 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2352 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2353 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2354 if (sign) {
2355 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2356 }
2357 }
2358 }
2359
Cary Clarkdf429f32017-11-08 11:44:31 -05002360 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2361 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2362 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2363 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002364 fLastVec.dot(curVec) < 0.0f) {
2365 return kBackwards_DirChange;
2366 }
2367
2368 return kStraight_DirChange;
2369 }
2370
Cary Clarkc8323aa2017-08-25 08:04:43 -04002371 bool hasBackwards() const {
2372 return fBackwards;
2373 }
caryclarkb4216502015-03-02 13:02:34 -08002374
caryclarkd3d1a982014-12-08 04:57:38 -08002375 bool isFinite() const {
2376 return fIsFinite;
2377 }
2378
caryclark5ccef572015-03-02 10:07:56 -08002379 void setCurve(bool isCurve) {
2380 fIsCurve = isCurve;
2381 }
2382
reed@google.com04863fa2011-05-15 04:08:24 +00002383private:
2384 void addVec(const SkVector& vec) {
2385 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002386 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002387 switch (dir) {
2388 case kLeft_DirChange: // fall through
2389 case kRight_DirChange:
2390 if (kInvalid_DirChange == fExpectedDir) {
2391 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002392 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2393 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002394 } else if (dir != fExpectedDir) {
2395 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002396 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002397 }
2398 fLastVec = vec;
2399 break;
2400 case kStraight_DirChange:
2401 break;
2402 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002403 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002404 // If any of the subsequent dir is non-backward, it'll be concave.
2405 // Otherwise, it's still convex.
2406 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002407 }
robertphillipsc506e302014-09-16 09:43:31 -07002408 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002409 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002410 break;
2411 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002412 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002413 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002414 }
2415 }
2416
caryclarkb4216502015-03-02 13:02:34 -08002417 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002418 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002419 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002420 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2421 // value with the current vec is deemed to be of a significant value.
2422 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002423 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002424 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002425 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002426 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002427 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002428 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002429 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002430 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002431};
2432
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002433SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002434 // Sometimes we think we need to calculate convexity but another thread already did.
2435 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2436 return c;
2437 }
2438
reed@google.com04863fa2011-05-15 04:08:24 +00002439 SkPoint pts[4];
2440 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002441 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002442
2443 int contourCount = 0;
2444 int count;
2445 Convexicator state;
2446
caryclarkd3d1a982014-12-08 04:57:38 -08002447 if (!isFinite()) {
2448 return kUnknown_Convexity;
2449 }
Brian Osman205a1262017-09-18 13:13:48 +00002450 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002451 switch (verb) {
2452 case kMove_Verb:
2453 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002454 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002455 return kConcave_Convexity;
2456 }
2457 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002458 // fall through
2459 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002460 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002461 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002462 break;
caryclark5ccef572015-03-02 10:07:56 -08002463 case kQuad_Verb:
2464 // fall through
2465 case kConic_Verb:
2466 // fall through
2467 case kCubic_Verb:
2468 count = 2 + (kCubic_Verb == verb);
2469 // As an additional enhancement, this could set curve true only
2470 // if the curve is nonlinear
2471 state.setCurve(true);
2472 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002473 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002474 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002475 state.close();
2476 count = 0;
2477 break;
2478 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002479 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002480 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002481 return kConcave_Convexity;
2482 }
2483
2484 for (int i = 1; i <= count; i++) {
2485 state.addPt(pts[i]);
2486 }
2487 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002488 if (!state.isFinite()) {
2489 return kUnknown_Convexity;
2490 }
reed@google.com04863fa2011-05-15 04:08:24 +00002491 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002492 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002493 return kConcave_Convexity;
2494 }
2495 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002496 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002497 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002498 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2499 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2500 fConvexity = Convexity::kConcave_Convexity;
2501 } else {
2502 fFirstDirection = state.getFirstDirection();
2503 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002504 }
2505 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002506}
reed@google.com69a99432012-01-10 18:00:10 +00002507
2508///////////////////////////////////////////////////////////////////////////////
2509
2510class ContourIter {
2511public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002512 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002513
2514 bool done() const { return fDone; }
2515 // if !done() then these may be called
2516 int count() const { return fCurrPtCount; }
2517 const SkPoint* pts() const { return fCurrPt; }
2518 void next();
2519
2520private:
2521 int fCurrPtCount;
2522 const SkPoint* fCurrPt;
2523 const uint8_t* fCurrVerb;
2524 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002525 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002526 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002527 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002528};
2529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530ContourIter::ContourIter(const SkPathRef& pathRef) {
2531 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002532 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002533 fCurrPt = pathRef.points();
2534 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002535 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002536 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002537 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002538 this->next();
2539}
2540
2541void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002542 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002543 fDone = true;
2544 }
2545 if (fDone) {
2546 return;
2547 }
2548
2549 // skip pts of prev contour
2550 fCurrPt += fCurrPtCount;
2551
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002552 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002553 int ptCount = 1; // moveTo
2554 const uint8_t* verbs = fCurrVerb;
2555
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002556 for (--verbs; verbs > fStopVerbs; --verbs) {
2557 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002558 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002559 goto CONTOUR_END;
2560 case SkPath::kLine_Verb:
2561 ptCount += 1;
2562 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002563 case SkPath::kConic_Verb:
2564 fCurrConicWeight += 1;
2565 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002566 case SkPath::kQuad_Verb:
2567 ptCount += 2;
2568 break;
2569 case SkPath::kCubic_Verb:
2570 ptCount += 3;
2571 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002572 case SkPath::kClose_Verb:
2573 break;
2574 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002575 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002576 break;
2577 }
2578 }
2579CONTOUR_END:
2580 fCurrPtCount = ptCount;
2581 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002582 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002583}
2584
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002585// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002586static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002587 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2588 // We may get 0 when the above subtracts underflow. We expect this to be
2589 // very rare and lazily promote to double.
2590 if (0 == cross) {
2591 double p0x = SkScalarToDouble(p0.fX);
2592 double p0y = SkScalarToDouble(p0.fY);
2593
2594 double p1x = SkScalarToDouble(p1.fX);
2595 double p1y = SkScalarToDouble(p1.fY);
2596
2597 double p2x = SkScalarToDouble(p2.fX);
2598 double p2y = SkScalarToDouble(p2.fY);
2599
2600 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2601 (p1y - p0y) * (p2x - p0x));
2602
2603 }
2604 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002605}
2606
reed@google.comc1ea60a2012-01-31 15:15:36 +00002607// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002608static int find_max_y(const SkPoint pts[], int count) {
2609 SkASSERT(count > 0);
2610 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002611 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002612 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002613 SkScalar y = pts[i].fY;
2614 if (y > max) {
2615 max = y;
2616 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002617 }
2618 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002619 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002620}
2621
reed@google.comcabaf1d2012-01-11 21:03:05 +00002622static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2623 int i = index;
2624 for (;;) {
2625 i = (i + inc) % n;
2626 if (i == index) { // we wrapped around, so abort
2627 break;
2628 }
2629 if (pts[index] != pts[i]) { // found a different point, success!
2630 break;
2631 }
2632 }
2633 return i;
2634}
2635
reed@google.comc1ea60a2012-01-31 15:15:36 +00002636/**
2637 * Starting at index, and moving forward (incrementing), find the xmin and
2638 * xmax of the contiguous points that have the same Y.
2639 */
2640static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2641 int* maxIndexPtr) {
2642 const SkScalar y = pts[index].fY;
2643 SkScalar min = pts[index].fX;
2644 SkScalar max = min;
2645 int minIndex = index;
2646 int maxIndex = index;
2647 for (int i = index + 1; i < n; ++i) {
2648 if (pts[i].fY != y) {
2649 break;
2650 }
2651 SkScalar x = pts[i].fX;
2652 if (x < min) {
2653 min = x;
2654 minIndex = i;
2655 } else if (x > max) {
2656 max = x;
2657 maxIndex = i;
2658 }
2659 }
2660 *maxIndexPtr = maxIndex;
2661 return minIndex;
2662}
2663
reed026beb52015-06-10 14:23:15 -07002664static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2665 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002666}
2667
reed@google.comac8543f2012-01-30 20:51:25 +00002668/*
2669 * We loop through all contours, and keep the computed cross-product of the
2670 * contour that contained the global y-max. If we just look at the first
2671 * contour, we may find one that is wound the opposite way (correctly) since
2672 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2673 * that is outer most (or at least has the global y-max) before we can consider
2674 * its cross product.
2675 */
reed026beb52015-06-10 14:23:15 -07002676bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002677 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2678 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002679 return true;
2680 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002681
2682 // don't want to pay the cost for computing this if it
2683 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002684 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2685 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002686 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687 return false;
2688 }
reed@google.com69a99432012-01-10 18:00:10 +00002689
reed026beb52015-06-10 14:23:15 -07002690 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002691
reed@google.comac8543f2012-01-30 20:51:25 +00002692 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002693 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002694 SkScalar ymaxCross = 0;
2695
reed@google.com69a99432012-01-10 18:00:10 +00002696 for (; !iter.done(); iter.next()) {
2697 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002698 if (n < 3) {
2699 continue;
2700 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002701
reed@google.comcabaf1d2012-01-11 21:03:05 +00002702 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002703 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002704 int index = find_max_y(pts, n);
2705 if (pts[index].fY < ymax) {
2706 continue;
2707 }
2708
2709 // If there is more than 1 distinct point at the y-max, we take the
2710 // x-min and x-max of them and just subtract to compute the dir.
2711 if (pts[(index + 1) % n].fY == pts[index].fY) {
2712 int maxIndex;
2713 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2714 if (minIndex == maxIndex) {
2715 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002716 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002717 SkASSERT(pts[minIndex].fY == pts[index].fY);
2718 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2719 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2720 // we just subtract the indices, and let that auto-convert to
2721 // SkScalar, since we just want - or + to signal the direction.
2722 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002723 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002724 TRY_CROSSPROD:
2725 // Find a next and prev index to use for the cross-product test,
2726 // but we try to find pts that form non-zero vectors from pts[index]
2727 //
2728 // Its possible that we can't find two non-degenerate vectors, so
2729 // we have to guard our search (e.g. all the pts could be in the
2730 // same place).
2731
2732 // we pass n - 1 instead of -1 so we don't foul up % operator by
2733 // passing it a negative LH argument.
2734 int prev = find_diff_pt(pts, index, n, n - 1);
2735 if (prev == index) {
2736 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002737 continue;
2738 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002739 int next = find_diff_pt(pts, index, n, 1);
2740 SkASSERT(next != index);
2741 cross = cross_prod(pts[prev], pts[index], pts[next]);
2742 // if we get a zero and the points are horizontal, then we look at the spread in
2743 // x-direction. We really should continue to walk away from the degeneracy until
2744 // there is a divergence.
2745 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2746 // construct the subtract so we get the correct Direction below
2747 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002748 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002749 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002750
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002751 if (cross) {
2752 // record our best guess so far
2753 ymax = pts[index].fY;
2754 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002755 }
2756 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002757 if (ymaxCross) {
2758 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002759 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002760 return true;
2761 } else {
2762 return false;
2763 }
reed@google.comac8543f2012-01-30 20:51:25 +00002764}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765
2766///////////////////////////////////////////////////////////////////////////////
2767
caryclark9aacd902015-12-14 08:38:09 -08002768static bool between(SkScalar a, SkScalar b, SkScalar c) {
2769 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2770 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2771 return (a - b) * (c - b) <= 0;
2772}
2773
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002774static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2775 SkScalar t) {
2776 SkScalar A = c3 + 3*(c1 - c2) - c0;
2777 SkScalar B = 3*(c2 - c1 - c1 + c0);
2778 SkScalar C = 3*(c1 - c0);
2779 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002780 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781}
2782
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783template <size_t N> static void find_minmax(const SkPoint pts[],
2784 SkScalar* minPtr, SkScalar* maxPtr) {
2785 SkScalar min, max;
2786 min = max = pts[0].fX;
2787 for (size_t i = 1; i < N; ++i) {
2788 min = SkMinScalar(min, pts[i].fX);
2789 max = SkMaxScalar(max, pts[i].fX);
2790 }
2791 *minPtr = min;
2792 *maxPtr = max;
2793}
2794
caryclark9cb5d752015-12-18 04:35:24 -08002795static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2796 if (start.fY == end.fY) {
2797 return between(start.fX, x, end.fX) && x != end.fX;
2798 } else {
2799 return x == start.fX && y == start.fY;
2800 }
2801}
2802
caryclark9aacd902015-12-14 08:38:09 -08002803static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002804 SkScalar y0 = pts[0].fY;
2805 SkScalar y3 = pts[3].fY;
2806
2807 int dir = 1;
2808 if (y0 > y3) {
2809 SkTSwap(y0, y3);
2810 dir = -1;
2811 }
2812 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 return 0;
2814 }
caryclark9cb5d752015-12-18 04:35:24 -08002815 if (checkOnCurve(x, y, pts[0], pts[3])) {
2816 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002817 return 0;
2818 }
caryclark9cb5d752015-12-18 04:35:24 -08002819 if (y == y3) {
2820 return 0;
2821 }
2822
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002823 // quickreject or quickaccept
2824 SkScalar min, max;
2825 find_minmax<4>(pts, &min, &max);
2826 if (x < min) {
2827 return 0;
2828 }
2829 if (x > max) {
2830 return dir;
2831 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002832
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002834 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002835 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002836 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002837 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002838 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002839 if (SkScalarNearlyEqual(xt, x)) {
2840 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2841 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002842 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002843 }
2844 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 return xt < x ? dir : 0;
2846}
2847
caryclark9aacd902015-12-14 08:38:09 -08002848static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 SkPoint dst[10];
2850 int n = SkChopCubicAtYExtrema(pts, dst);
2851 int w = 0;
2852 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002853 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002854 }
2855 return w;
2856}
2857
caryclark9aacd902015-12-14 08:38:09 -08002858static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2859 SkASSERT(src);
2860 SkASSERT(t >= 0 && t <= 1);
2861 SkScalar src2w = src[2] * w;
2862 SkScalar C = src[0];
2863 SkScalar A = src[4] - 2 * src2w + C;
2864 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002865 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002866}
2867
2868
2869static double conic_eval_denominator(SkScalar w, SkScalar t) {
2870 SkScalar B = 2 * (w - 1);
2871 SkScalar C = 1;
2872 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002873 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002874}
2875
2876static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2877 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 SkScalar y0 = pts[0].fY;
2879 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 int dir = 1;
2882 if (y0 > y2) {
2883 SkTSwap(y0, y2);
2884 dir = -1;
2885 }
caryclark9aacd902015-12-14 08:38:09 -08002886 if (y < y0 || y > y2) {
2887 return 0;
2888 }
caryclark9cb5d752015-12-18 04:35:24 -08002889 if (checkOnCurve(x, y, pts[0], pts[2])) {
2890 *onCurveCount += 1;
2891 return 0;
2892 }
caryclark9aacd902015-12-14 08:38:09 -08002893 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 return 0;
2895 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002896
caryclark9aacd902015-12-14 08:38:09 -08002897 SkScalar roots[2];
2898 SkScalar A = pts[2].fY;
2899 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2900 SkScalar C = pts[0].fY;
2901 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2902 B -= C; // B = b*w - w * yCept + yCept - a
2903 C -= y;
2904 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2905 SkASSERT(n <= 1);
2906 SkScalar xt;
2907 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002908 // zero roots are returned only when y0 == y
2909 // Need [0] if dir == 1
2910 // and [2] if dir == -1
2911 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002912 } else {
2913 SkScalar t = roots[0];
2914 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2915 }
2916 if (SkScalarNearlyEqual(xt, x)) {
2917 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2918 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002919 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002920 }
2921 }
2922 return xt < x ? dir : 0;
2923}
2924
2925static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2926 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2927 if (y0 == y1) {
2928 return true;
2929 }
2930 if (y0 < y1) {
2931 return y1 <= y2;
2932 } else {
2933 return y1 >= y2;
2934 }
2935}
2936
2937static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2938 int* onCurveCount) {
2939 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002940 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002941 // If the data points are very large, the conic may not be monotonic but may also
2942 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002943 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2944 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2945 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002946 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2947 }
2948 return w;
2949}
2950
2951static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2952 SkScalar y0 = pts[0].fY;
2953 SkScalar y2 = pts[2].fY;
2954
2955 int dir = 1;
2956 if (y0 > y2) {
2957 SkTSwap(y0, y2);
2958 dir = -1;
2959 }
2960 if (y < y0 || y > y2) {
2961 return 0;
2962 }
caryclark9cb5d752015-12-18 04:35:24 -08002963 if (checkOnCurve(x, y, pts[0], pts[2])) {
2964 *onCurveCount += 1;
2965 return 0;
2966 }
caryclark9aacd902015-12-14 08:38:09 -08002967 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002968 return 0;
2969 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002970 // bounds check on X (not required. is it faster?)
2971#if 0
2972 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2973 return 0;
2974 }
2975#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002976
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 SkScalar roots[2];
2978 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2979 2 * (pts[1].fY - pts[0].fY),
2980 pts[0].fY - y,
2981 roots);
2982 SkASSERT(n <= 1);
2983 SkScalar xt;
2984 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002985 // zero roots are returned only when y0 == y
2986 // Need [0] if dir == 1
2987 // and [2] if dir == -1
2988 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 } else {
2990 SkScalar t = roots[0];
2991 SkScalar C = pts[0].fX;
2992 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2993 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002994 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 }
caryclark9aacd902015-12-14 08:38:09 -08002996 if (SkScalarNearlyEqual(xt, x)) {
2997 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2998 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002999 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003000 }
3001 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 return xt < x ? dir : 0;
3003}
3004
caryclark9aacd902015-12-14 08:38:09 -08003005static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 SkPoint dst[5];
3007 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003008
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3010 n = SkChopQuadAtYExtrema(pts, dst);
3011 pts = dst;
3012 }
caryclark9aacd902015-12-14 08:38:09 -08003013 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003014 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003015 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 }
3017 return w;
3018}
3019
caryclark9aacd902015-12-14 08:38:09 -08003020static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003021 SkScalar x0 = pts[0].fX;
3022 SkScalar y0 = pts[0].fY;
3023 SkScalar x1 = pts[1].fX;
3024 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003025
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003026 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003027
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003028 int dir = 1;
3029 if (y0 > y1) {
3030 SkTSwap(y0, y1);
3031 dir = -1;
3032 }
caryclark9aacd902015-12-14 08:38:09 -08003033 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003034 return 0;
3035 }
caryclark9cb5d752015-12-18 04:35:24 -08003036 if (checkOnCurve(x, y, pts[0], pts[1])) {
3037 *onCurveCount += 1;
3038 return 0;
3039 }
3040 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003041 return 0;
3042 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003043 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003044
caryclark9aacd902015-12-14 08:38:09 -08003045 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003046 // zero cross means the point is on the line, and since the case where
3047 // y of the query point is at the end point is handled above, we can be
3048 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003049 if (x != x1 || y != pts[1].fY) {
3050 *onCurveCount += 1;
3051 }
caryclark9aacd902015-12-14 08:38:09 -08003052 dir = 0;
3053 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003054 dir = 0;
3055 }
3056 return dir;
3057}
3058
caryclark9aacd902015-12-14 08:38:09 -08003059static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3060 SkTDArray<SkVector>* tangents) {
3061 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3062 && !between(pts[2].fY, y, pts[3].fY)) {
3063 return;
3064 }
3065 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3066 && !between(pts[2].fX, x, pts[3].fX)) {
3067 return;
3068 }
3069 SkPoint dst[10];
3070 int n = SkChopCubicAtYExtrema(pts, dst);
3071 for (int i = 0; i <= n; ++i) {
3072 SkPoint* c = &dst[i * 3];
3073 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003074 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003075 continue;
mbarbella276e6332016-05-31 14:44:01 -07003076 }
caryclark9aacd902015-12-14 08:38:09 -08003077 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3078 if (!SkScalarNearlyEqual(x, xt)) {
3079 continue;
3080 }
3081 SkVector tangent;
3082 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3083 tangents->push(tangent);
3084 }
3085}
3086
3087static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3088 SkTDArray<SkVector>* tangents) {
3089 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3090 return;
3091 }
3092 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3093 return;
3094 }
3095 SkScalar roots[2];
3096 SkScalar A = pts[2].fY;
3097 SkScalar B = pts[1].fY * w - y * w + y;
3098 SkScalar C = pts[0].fY;
3099 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3100 B -= C; // B = b*w - w * yCept + yCept - a
3101 C -= y;
3102 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3103 for (int index = 0; index < n; ++index) {
3104 SkScalar t = roots[index];
3105 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3106 if (!SkScalarNearlyEqual(x, xt)) {
3107 continue;
3108 }
3109 SkConic conic(pts, w);
3110 tangents->push(conic.evalTangentAt(t));
3111 }
3112}
3113
3114static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3115 SkTDArray<SkVector>* tangents) {
3116 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3117 return;
3118 }
3119 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3120 return;
3121 }
3122 SkScalar roots[2];
3123 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3124 2 * (pts[1].fY - pts[0].fY),
3125 pts[0].fY - y,
3126 roots);
3127 for (int index = 0; index < n; ++index) {
3128 SkScalar t = roots[index];
3129 SkScalar C = pts[0].fX;
3130 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3131 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003132 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003133 if (!SkScalarNearlyEqual(x, xt)) {
3134 continue;
3135 }
3136 tangents->push(SkEvalQuadTangentAt(pts, t));
3137 }
3138}
3139
3140static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3141 SkTDArray<SkVector>* tangents) {
3142 SkScalar y0 = pts[0].fY;
3143 SkScalar y1 = pts[1].fY;
3144 if (!between(y0, y, y1)) {
3145 return;
3146 }
3147 SkScalar x0 = pts[0].fX;
3148 SkScalar x1 = pts[1].fX;
3149 if (!between(x0, x, x1)) {
3150 return;
3151 }
3152 SkScalar dx = x1 - x0;
3153 SkScalar dy = y1 - y0;
3154 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3155 return;
3156 }
3157 SkVector v;
3158 v.set(dx, dy);
3159 tangents->push(v);
3160}
3161
reed@google.com4db592c2013-10-30 17:39:43 +00003162static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3163 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3164}
3165
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003166bool SkPath::contains(SkScalar x, SkScalar y) const {
3167 bool isInverse = this->isInverseFillType();
3168 if (this->isEmpty()) {
3169 return isInverse;
3170 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003171
reed@google.com4db592c2013-10-30 17:39:43 +00003172 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003173 return isInverse;
3174 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003175
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176 SkPath::Iter iter(*this, true);
3177 bool done = false;
3178 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003179 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003180 do {
3181 SkPoint pts[4];
3182 switch (iter.next(pts, false)) {
3183 case SkPath::kMove_Verb:
3184 case SkPath::kClose_Verb:
3185 break;
3186 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003187 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 break;
3189 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003190 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003191 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003192 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003193 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003194 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003195 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003196 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003197 break;
3198 case SkPath::kDone_Verb:
3199 done = true;
3200 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003201 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003202 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003203 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3204 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3205 if (evenOddFill) {
3206 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003207 }
caryclark9aacd902015-12-14 08:38:09 -08003208 if (w) {
3209 return !isInverse;
3210 }
3211 if (onCurveCount <= 1) {
3212 return SkToBool(onCurveCount) ^ isInverse;
3213 }
3214 if ((onCurveCount & 1) || evenOddFill) {
3215 return SkToBool(onCurveCount & 1) ^ isInverse;
3216 }
halcanary9d524f22016-03-29 09:03:52 -07003217 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003218 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3219 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003220 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003221 SkTDArray<SkVector> tangents;
3222 do {
3223 SkPoint pts[4];
3224 int oldCount = tangents.count();
3225 switch (iter.next(pts, false)) {
3226 case SkPath::kMove_Verb:
3227 case SkPath::kClose_Verb:
3228 break;
3229 case SkPath::kLine_Verb:
3230 tangent_line(pts, x, y, &tangents);
3231 break;
3232 case SkPath::kQuad_Verb:
3233 tangent_quad(pts, x, y, &tangents);
3234 break;
3235 case SkPath::kConic_Verb:
3236 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3237 break;
3238 case SkPath::kCubic_Verb:
3239 tangent_cubic(pts, x, y, &tangents);
3240 break;
3241 case SkPath::kDone_Verb:
3242 done = true;
3243 break;
3244 }
3245 if (tangents.count() > oldCount) {
3246 int last = tangents.count() - 1;
3247 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003248 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003249 tangents.remove(last);
3250 } else {
3251 for (int index = 0; index < last; ++index) {
3252 const SkVector& test = tangents[index];
3253 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003254 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3255 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003256 tangents.remove(last);
3257 tangents.removeShuffle(index);
3258 break;
3259 }
3260 }
3261 }
3262 }
3263 } while (!done);
3264 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003265}
fmalitaaa0df4e2015-12-01 09:13:23 -08003266
3267int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3268 SkScalar w, SkPoint pts[], int pow2) {
3269 const SkConic conic(p0, p1, p2, w);
3270 return conic.chopIntoQuadsPOW2(pts, pow2);
3271}
bsalomonedc743a2016-06-01 09:42:31 -07003272
3273bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3274 unsigned* start) {
3275 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3276 return false;
3277 }
3278 SkPath::RawIter iter(path);
3279 SkPoint verbPts[4];
3280 SkPath::Verb v;
3281 SkPoint rectPts[5];
3282 int rectPtCnt = 0;
3283 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3284 switch (v) {
3285 case SkPath::kMove_Verb:
3286 if (0 != rectPtCnt) {
3287 return false;
3288 }
3289 rectPts[0] = verbPts[0];
3290 ++rectPtCnt;
3291 break;
3292 case SkPath::kLine_Verb:
3293 if (5 == rectPtCnt) {
3294 return false;
3295 }
3296 rectPts[rectPtCnt] = verbPts[1];
3297 ++rectPtCnt;
3298 break;
3299 case SkPath::kClose_Verb:
3300 if (4 == rectPtCnt) {
3301 rectPts[4] = rectPts[0];
3302 rectPtCnt = 5;
3303 }
3304 break;
3305 default:
3306 return false;
3307 }
3308 }
3309 if (rectPtCnt < 5) {
3310 return false;
3311 }
3312 if (rectPts[0] != rectPts[4]) {
3313 return false;
3314 }
bsalomon057ae8a2016-07-24 05:37:26 -07003315 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3316 // and pts 1 and 2 the opposite vertical or horizontal edge).
3317 bool vec03IsVertical;
3318 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3319 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3320 // Make sure it has non-zero width and height
3321 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003322 return false;
3323 }
bsalomon057ae8a2016-07-24 05:37:26 -07003324 vec03IsVertical = true;
3325 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3326 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3327 // Make sure it has non-zero width and height
3328 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3329 return false;
3330 }
3331 vec03IsVertical = false;
3332 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003333 return false;
3334 }
bsalomon057ae8a2016-07-24 05:37:26 -07003335 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3336 // set if it is on the bottom edge.
3337 unsigned sortFlags =
3338 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3339 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3340 switch (sortFlags) {
3341 case 0b00:
3342 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3343 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3344 *start = 0;
3345 break;
3346 case 0b01:
3347 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3348 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3349 *start = 1;
3350 break;
3351 case 0b10:
3352 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3353 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3354 *start = 3;
3355 break;
3356 case 0b11:
3357 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3358 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3359 *start = 2;
3360 break;
bsalomonedc743a2016-06-01 09:42:31 -07003361 }
bsalomonedc743a2016-06-01 09:42:31 -07003362 return true;
3363}
bsalomon21af9ca2016-08-25 12:29:23 -07003364
Brian Salomone4949402018-04-26 15:22:04 -04003365bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3366 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3367 // This gets converted to an oval.
3368 return true;
3369 }
3370 if (useCenter) {
3371 // This is a pie wedge. It's convex if the angle is <= 180.
3372 return SkScalarAbs(sweepAngle) <= 180.f;
3373 }
3374 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3375 // to a secant, i.e. convex.
3376 return SkScalarAbs(sweepAngle) <= 360.f;
3377}
3378
bsalomon21af9ca2016-08-25 12:29:23 -07003379void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3380 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3381 SkASSERT(!oval.isEmpty());
3382 SkASSERT(sweepAngle);
3383
3384 path->reset();
3385 path->setIsVolatile(true);
3386 path->setFillType(SkPath::kWinding_FillType);
3387 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3388 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003389 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003390 return;
3391 }
3392 if (useCenter) {
3393 path->moveTo(oval.centerX(), oval.centerY());
3394 }
Brian Salomone4949402018-04-26 15:22:04 -04003395 auto firstDir =
3396 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3397 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003398 // Arc to mods at 360 and drawArc is not supposed to.
3399 bool forceMoveTo = !useCenter;
3400 while (sweepAngle <= -360.f) {
3401 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3402 startAngle -= 180.f;
3403 path->arcTo(oval, startAngle, -180.f, false);
3404 startAngle -= 180.f;
3405 forceMoveTo = false;
3406 sweepAngle += 360.f;
3407 }
3408 while (sweepAngle >= 360.f) {
3409 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3410 startAngle += 180.f;
3411 path->arcTo(oval, startAngle, 180.f, false);
3412 startAngle += 180.f;
3413 forceMoveTo = false;
3414 sweepAngle -= 360.f;
3415 }
3416 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3417 if (useCenter) {
3418 path->close();
3419 }
Brian Salomone4949402018-04-26 15:22:04 -04003420 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3421 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003422}
Mike Reed0d7dac82017-02-02 17:45:56 -08003423
3424///////////////////////////////////////////////////////////////////////////////////////////////////
3425#include "SkNx.h"
3426
3427static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3428 SkScalar ts[2];
3429 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3430 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3431 SkASSERT(n >= 0 && n <= 2);
3432 for (int i = 0; i < n; ++i) {
3433 extremas[i] = SkEvalQuadAt(src, ts[i]);
3434 }
3435 extremas[n] = src[2];
3436 return n + 1;
3437}
3438
3439static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3440 SkConic conic(src[0], src[1], src[2], w);
3441 SkScalar ts[2];
3442 int n = conic.findXExtrema(ts);
3443 n += conic.findYExtrema(&ts[n]);
3444 SkASSERT(n >= 0 && n <= 2);
3445 for (int i = 0; i < n; ++i) {
3446 extremas[i] = conic.evalAt(ts[i]);
3447 }
3448 extremas[n] = src[2];
3449 return n + 1;
3450}
3451
3452static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3453 SkScalar ts[4];
3454 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3455 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3456 SkASSERT(n >= 0 && n <= 4);
3457 for (int i = 0; i < n; ++i) {
3458 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3459 }
3460 extremas[n] = src[3];
3461 return n + 1;
3462}
3463
Mike Reed8d3196b2017-02-03 11:34:13 -05003464SkRect SkPath::computeTightBounds() const {
3465 if (0 == this->countVerbs()) {
3466 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003467 }
3468
Mike Reed8d3196b2017-02-03 11:34:13 -05003469 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3470 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003471 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003472
Mike Reed0d7dac82017-02-02 17:45:56 -08003473 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3474 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003475 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003476
3477 // initial with the first MoveTo, so we don't have to check inside the switch
3478 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003479 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003480 for (;;) {
3481 int count = 0;
3482 switch (iter.next(pts)) {
3483 case SkPath::kMove_Verb:
3484 extremas[0] = pts[0];
3485 count = 1;
3486 break;
3487 case SkPath::kLine_Verb:
3488 extremas[0] = pts[1];
3489 count = 1;
3490 break;
3491 case SkPath::kQuad_Verb:
3492 count = compute_quad_extremas(pts, extremas);
3493 break;
3494 case SkPath::kConic_Verb:
3495 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3496 break;
3497 case SkPath::kCubic_Verb:
3498 count = compute_cubic_extremas(pts, extremas);
3499 break;
3500 case SkPath::kClose_Verb:
3501 break;
3502 case SkPath::kDone_Verb:
3503 goto DONE;
3504 }
3505 for (int i = 0; i < count; ++i) {
3506 Sk2s tmp = from_point(extremas[i]);
3507 min = Sk2s::Min(min, tmp);
3508 max = Sk2s::Max(max, tmp);
3509 }
3510 }
3511DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003512 SkRect bounds;
3513 min.store((SkPoint*)&bounds.fLeft);
3514 max.store((SkPoint*)&bounds.fRight);
3515 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003516}
Cary Clarkdf429f32017-11-08 11:44:31 -05003517
3518bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3519 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3520}
3521
3522bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3523 const SkPoint& p3, bool exact) {
3524 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3525 SkPointPriv::EqualsWithinTolerance(p2, p3);
3526}
3527
3528bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3529 const SkPoint& p3, const SkPoint& p4, bool exact) {
3530 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3531 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3532 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3533 SkPointPriv::EqualsWithinTolerance(p3, p4);
3534}