blob: e38d73629475c2206ae15701e478056475f37097 [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 Canaryc640d0d2018-06-13 09:59:02 -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 Canaryc640d0d2018-06-13 09:59:02 -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);
Ben Wagnercee46e52018-06-12 16:30:29 -0400205 SkTSwap(fLastMoveToIndex, that.fLastMoveToIndex);
206 SkTSwap(fFillType, that.fFillType);
207 SkTSwap(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
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824SkPath::Iter::Iter() {
1825#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001826 fPts = nullptr;
1827 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001830 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831#endif
1832 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001833 fVerbs = nullptr;
1834 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 fNeedClose = false;
1836}
1837
1838SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1839 this->setPath(path, forceClose);
1840}
1841
1842void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001843 fPts = path.fPathRef->points();
1844 fVerbs = path.fPathRef->verbs();
1845 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001846 fConicWeights = path.fPathRef->conicWeights();
1847 if (fConicWeights) {
1848 fConicWeights -= 1; // begin one behind
1849 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001850 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001851 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001852 fForceClose = SkToU8(forceClose);
1853 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001854 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855}
1856
1857bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001858 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 return false;
1860 }
1861 if (fForceClose) {
1862 return true;
1863 }
1864
1865 const uint8_t* verbs = fVerbs;
1866 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 if (kMove_Verb == *(verbs - 1)) {
1869 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 }
1871
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001872 while (verbs > stop) {
1873 // verbs points one beyond the current verb, decrement first.
1874 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875 if (kMove_Verb == v) {
1876 break;
1877 }
1878 if (kClose_Verb == v) {
1879 return true;
1880 }
1881 }
1882 return false;
1883}
1884
1885SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001886 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001887 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001888 // A special case: if both points are NaN, SkPoint::operation== returns
1889 // false, but the iterator expects that they are treated as the same.
1890 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001891 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1892 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001893 return kClose_Verb;
1894 }
1895
reed@google.com9e25dbf2012-05-15 17:05:38 +00001896 pts[0] = fLastPt;
1897 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 fLastPt = fMoveTo;
1899 fCloseLine = true;
1900 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001901 } else {
1902 pts[0] = fMoveTo;
1903 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905}
1906
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 if (fSegmentState == kAfterMove_SegmentState) {
1909 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001911 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001913
1914 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1915 // Set the first return pt to the last pt of the previous primitive.
1916 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917}
1918
caryclarke8c56662015-07-14 11:19:26 -07001919void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 // We need to step over anything that will not move the current draw point
1921 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001922 const uint8_t* lastMoveVerb = nullptr;
1923 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001924 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 SkPoint lastPt = fLastPt;
1926 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001927 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001928 switch (verb) {
1929 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 // Keep a record of this most recent move
1931 lastMoveVerb = fVerbs;
1932 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001933 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001934 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001935 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 fPts++;
1937 break;
1938
1939 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001940 // A close when we are in a segment is always valid except when it
1941 // follows a move which follows a segment.
1942 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 return;
1944 }
1945 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001946 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 break;
1948
1949 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001950 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 if (lastMoveVerb) {
1952 fVerbs = lastMoveVerb;
1953 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001954 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001955 return;
1956 }
1957 return;
1958 }
1959 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001960 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 fPts++;
1962 break;
1963
reed@google.com277c3f82013-05-31 15:17:50 +00001964 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001965 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001966 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967 if (lastMoveVerb) {
1968 fVerbs = lastMoveVerb;
1969 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001970 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 return;
1972 }
1973 return;
1974 }
1975 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001976 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001978 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 break;
1980
1981 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001982 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 if (lastMoveVerb) {
1984 fVerbs = lastMoveVerb;
1985 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001986 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 return;
1988 }
1989 return;
1990 }
1991 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001992 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 fPts += 3;
1994 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001995
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001996 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001997 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998 }
1999 }
2000}
2001
reed@google.com4a3b7142012-05-16 17:16:46 +00002002SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002003 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002004
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002006 // Close the curve if requested and if there is some curve to close
2007 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002008 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 return kLine_Verb;
2010 }
2011 fNeedClose = false;
2012 return kClose_Verb;
2013 }
2014 return kDone_Verb;
2015 }
2016
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002017 // fVerbs is one beyond the current verb, decrement first
2018 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002019 const SkPoint* SK_RESTRICT srcPts = fPts;
2020 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021
2022 switch (verb) {
2023 case kMove_Verb:
2024 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002025 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 verb = this->autoClose(pts);
2027 if (verb == kClose_Verb) {
2028 fNeedClose = false;
2029 }
2030 return (Verb)verb;
2031 }
2032 if (fVerbs == fVerbStop) { // might be a trailing moveto
2033 return kDone_Verb;
2034 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002035 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002036 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002038 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002039 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 fNeedClose = fForceClose;
2041 break;
2042 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002043 pts[0] = this->cons_moveTo();
2044 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045 fLastPt = srcPts[0];
2046 fCloseLine = false;
2047 srcPts += 1;
2048 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002049 case kConic_Verb:
2050 fConicWeights += 1;
2051 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002053 pts[0] = this->cons_moveTo();
2054 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055 fLastPt = srcPts[1];
2056 srcPts += 2;
2057 break;
2058 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002059 pts[0] = this->cons_moveTo();
2060 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 fLastPt = srcPts[2];
2062 srcPts += 3;
2063 break;
2064 case kClose_Verb:
2065 verb = this->autoClose(pts);
2066 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002067 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068 } else {
2069 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002070 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002072 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073 break;
2074 }
2075 fPts = srcPts;
2076 return (Verb)verb;
2077}
2078
2079///////////////////////////////////////////////////////////////////////////////
2080
Ben Wagner4d1955c2017-03-10 13:08:15 -05002081#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002082#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002083#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002084
reed@google.com51bbe752013-01-17 22:07:50 +00002085static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002086 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002087 str->append(label);
2088 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002089
reed@google.com51bbe752013-01-17 22:07:50 +00002090 const SkScalar* values = &pts[0].fX;
2091 count *= 2;
2092
2093 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002094 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002095 if (i < count - 1) {
2096 str->append(", ");
2097 }
2098 }
Mike Reed405b9d22018-01-09 15:11:08 -05002099 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002100 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002101 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002102 }
caryclark08fa28c2014-10-23 13:08:56 -07002103 str->append(");");
reede05fed02014-12-15 07:59:53 -08002104 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002105 str->append(" // ");
2106 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002107 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002108 if (i < count - 1) {
2109 str->append(", ");
2110 }
2111 }
2112 if (conicWeight >= 0) {
2113 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002114 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002115 }
2116 }
2117 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002118}
2119
caryclarke9562592014-09-15 09:26:09 -07002120void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002121 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 Iter iter(*this, forceClose);
2123 SkPoint pts[4];
2124 Verb verb;
2125
reed@google.com51bbe752013-01-17 22:07:50 +00002126 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002127 char const * const gFillTypeStrs[] = {
2128 "Winding",
2129 "EvenOdd",
2130 "InverseWinding",
2131 "InverseEvenOdd",
2132 };
2133 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2134 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002135 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136 switch (verb) {
2137 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002138 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 break;
2140 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002141 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142 break;
2143 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002144 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002146 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002147 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002148 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002150 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 break;
2152 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002153 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 break;
2155 default:
2156 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2157 verb = kDone_Verb; // stop the loop
2158 break;
2159 }
caryclark1049f122015-04-20 08:31:59 -07002160 if (!wStream && builder.size()) {
2161 SkDebugf("%s", builder.c_str());
2162 builder.reset();
2163 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164 }
caryclark66a5d8b2014-06-24 08:30:15 -07002165 if (wStream) {
2166 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002167 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168}
2169
reed@android.come522ca52009-11-23 20:10:41 +00002170void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002171 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002172}
2173
2174void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002175 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002176}
2177
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002178
Cary Clark0461e9f2017-08-25 15:13:38 -04002179bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002180 if ((fFillType & ~3) != 0) {
2181 return false;
2182 }
reed@google.comabf15c12011-01-18 20:35:51 +00002183
djsollen@google.com077348c2012-10-22 20:23:32 +00002184#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002185 if (!fBoundsIsDirty) {
2186 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002187
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002188 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002189 if (SkToBool(fIsFinite) != isFinite) {
2190 return false;
2191 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002192
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002193 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002194 // if we're empty, fBounds may be empty but translated, so we can't
2195 // necessarily compare to bounds directly
2196 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2197 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002198 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2199 return false;
2200 }
reed@android.come522ca52009-11-23 20:10:41 +00002201 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002202 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002203 if (!fBounds.isEmpty()) {
2204 return false;
2205 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002206 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002207 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002208 if (!fBounds.contains(bounds)) {
2209 return false;
2210 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002211 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002212 }
reed@android.come522ca52009-11-23 20:10:41 +00002213 }
2214 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002215#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002216 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002217}
reed@android.come522ca52009-11-23 20:10:41 +00002218
reed@google.com04863fa2011-05-15 04:08:24 +00002219///////////////////////////////////////////////////////////////////////////////
2220
reed@google.com0b7b9822011-05-16 12:29:27 +00002221static int sign(SkScalar x) { return x < 0; }
2222#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002223
robertphillipsc506e302014-09-16 09:43:31 -07002224enum DirChange {
2225 kLeft_DirChange,
2226 kRight_DirChange,
2227 kStraight_DirChange,
2228 kBackwards_DirChange,
2229
2230 kInvalid_DirChange
2231};
2232
2233
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002234static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002235 // The error epsilon was empirically derived; worse case round rects
2236 // with a mid point outset by 2x float epsilon in tests had an error
2237 // of 12.
2238 const int epsilon = 16;
2239 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2240 return false;
2241 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002242 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002243 int aBits = SkFloatAs2sCompliment(compA);
2244 int bBits = SkFloatAs2sCompliment(compB);
2245 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002246}
2247
caryclarkb4216502015-03-02 13:02:34 -08002248static bool approximately_zero_when_compared_to(double x, double y) {
2249 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002250}
2251
caryclarkb4216502015-03-02 13:02:34 -08002252
reed@google.com04863fa2011-05-15 04:08:24 +00002253// only valid for a single contour
2254struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002255 Convexicator()
2256 : fPtCount(0)
2257 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002258 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002259 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002260 , fIsCurve(false)
2261 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002262 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002263 // warnings
djsollen2f124632016-04-29 13:53:05 -07002264 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002265 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002266 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002267 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002268 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002269
2270 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002271 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002272 }
2273
2274 SkPath::Convexity getConvexity() const { return fConvexity; }
2275
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002276 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002277 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002278
reed@google.com04863fa2011-05-15 04:08:24 +00002279 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002280 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002281 return;
2282 }
2283
2284 if (0 == fPtCount) {
2285 fCurrPt = pt;
2286 ++fPtCount;
2287 } else {
2288 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002289 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002290 if (!SkScalarIsFinite(lengthSqd)) {
2291 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002292 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002293 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002294 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002295 fCurrPt = pt;
2296 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002297 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002298 } else {
2299 SkASSERT(fPtCount > 2);
2300 this->addVec(vec);
2301 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002302
reed@google.com85b6e392011-05-15 20:25:17 +00002303 int sx = sign(vec.fX);
2304 int sy = sign(vec.fY);
2305 fDx += (sx != fSx);
2306 fDy += (sy != fSy);
2307 fSx = sx;
2308 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002309
reed@google.com85b6e392011-05-15 20:25:17 +00002310 if (fDx > 3 || fDy > 3) {
2311 fConvexity = SkPath::kConcave_Convexity;
2312 }
reed@google.com04863fa2011-05-15 04:08:24 +00002313 }
2314 }
2315 }
2316
2317 void close() {
2318 if (fPtCount > 2) {
2319 this->addVec(fFirstVec);
2320 }
2321 }
2322
caryclarkb4216502015-03-02 13:02:34 -08002323 DirChange directionChange(const SkVector& curVec) {
2324 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2325
2326 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2327 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2328 largest = SkTMax(largest, -smallest);
2329
2330 if (!almost_equal(largest, largest + cross)) {
2331 int sign = SkScalarSignAsInt(cross);
2332 if (sign) {
2333 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2334 }
2335 }
2336
2337 if (cross) {
2338 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2339 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2340 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2341 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2342 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2343 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2344 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2345 if (sign) {
2346 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2347 }
2348 }
2349 }
2350
Cary Clarkdf429f32017-11-08 11:44:31 -05002351 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2352 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2353 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2354 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002355 fLastVec.dot(curVec) < 0.0f) {
2356 return kBackwards_DirChange;
2357 }
2358
2359 return kStraight_DirChange;
2360 }
2361
Cary Clarkc8323aa2017-08-25 08:04:43 -04002362 bool hasBackwards() const {
2363 return fBackwards;
2364 }
caryclarkb4216502015-03-02 13:02:34 -08002365
caryclarkd3d1a982014-12-08 04:57:38 -08002366 bool isFinite() const {
2367 return fIsFinite;
2368 }
2369
caryclark5ccef572015-03-02 10:07:56 -08002370 void setCurve(bool isCurve) {
2371 fIsCurve = isCurve;
2372 }
2373
reed@google.com04863fa2011-05-15 04:08:24 +00002374private:
2375 void addVec(const SkVector& vec) {
2376 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002377 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002378 switch (dir) {
2379 case kLeft_DirChange: // fall through
2380 case kRight_DirChange:
2381 if (kInvalid_DirChange == fExpectedDir) {
2382 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002383 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2384 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002385 } else if (dir != fExpectedDir) {
2386 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002387 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002388 }
2389 fLastVec = vec;
2390 break;
2391 case kStraight_DirChange:
2392 break;
2393 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002394 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002395 // If any of the subsequent dir is non-backward, it'll be concave.
2396 // Otherwise, it's still convex.
2397 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002398 }
robertphillipsc506e302014-09-16 09:43:31 -07002399 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002400 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002401 break;
2402 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002403 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002404 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002405 }
2406 }
2407
caryclarkb4216502015-03-02 13:02:34 -08002408 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002409 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002411 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2412 // value with the current vec is deemed to be of a significant value.
2413 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002414 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002415 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002416 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002417 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002418 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002419 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002420 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002421 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002422};
2423
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002424SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002425 // Sometimes we think we need to calculate convexity but another thread already did.
2426 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2427 return c;
2428 }
2429
reed@google.com04863fa2011-05-15 04:08:24 +00002430 SkPoint pts[4];
2431 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002432 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002433
2434 int contourCount = 0;
2435 int count;
2436 Convexicator state;
2437
caryclarkd3d1a982014-12-08 04:57:38 -08002438 if (!isFinite()) {
2439 return kUnknown_Convexity;
2440 }
Brian Osman205a1262017-09-18 13:13:48 +00002441 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002442 switch (verb) {
2443 case kMove_Verb:
2444 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002445 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002446 return kConcave_Convexity;
2447 }
2448 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002449 // fall through
2450 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002451 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002452 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002453 break;
caryclark5ccef572015-03-02 10:07:56 -08002454 case kQuad_Verb:
2455 // fall through
2456 case kConic_Verb:
2457 // fall through
2458 case kCubic_Verb:
2459 count = 2 + (kCubic_Verb == verb);
2460 // As an additional enhancement, this could set curve true only
2461 // if the curve is nonlinear
2462 state.setCurve(true);
2463 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002464 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002465 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002466 state.close();
2467 count = 0;
2468 break;
2469 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002470 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002471 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002472 return kConcave_Convexity;
2473 }
2474
2475 for (int i = 1; i <= count; i++) {
2476 state.addPt(pts[i]);
2477 }
2478 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002479 if (!state.isFinite()) {
2480 return kUnknown_Convexity;
2481 }
reed@google.com04863fa2011-05-15 04:08:24 +00002482 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002483 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002484 return kConcave_Convexity;
2485 }
2486 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002487 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002488 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002489 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2490 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2491 fConvexity = Convexity::kConcave_Convexity;
2492 } else {
2493 fFirstDirection = state.getFirstDirection();
2494 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002495 }
2496 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002497}
reed@google.com69a99432012-01-10 18:00:10 +00002498
2499///////////////////////////////////////////////////////////////////////////////
2500
2501class ContourIter {
2502public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002503 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002504
2505 bool done() const { return fDone; }
2506 // if !done() then these may be called
2507 int count() const { return fCurrPtCount; }
2508 const SkPoint* pts() const { return fCurrPt; }
2509 void next();
2510
2511private:
2512 int fCurrPtCount;
2513 const SkPoint* fCurrPt;
2514 const uint8_t* fCurrVerb;
2515 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002516 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002517 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002518 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002519};
2520
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002521ContourIter::ContourIter(const SkPathRef& pathRef) {
2522 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002523 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002524 fCurrPt = pathRef.points();
2525 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002526 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002527 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002528 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002529 this->next();
2530}
2531
2532void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002533 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002534 fDone = true;
2535 }
2536 if (fDone) {
2537 return;
2538 }
2539
2540 // skip pts of prev contour
2541 fCurrPt += fCurrPtCount;
2542
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002543 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002544 int ptCount = 1; // moveTo
2545 const uint8_t* verbs = fCurrVerb;
2546
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002547 for (--verbs; verbs > fStopVerbs; --verbs) {
2548 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002549 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002550 goto CONTOUR_END;
2551 case SkPath::kLine_Verb:
2552 ptCount += 1;
2553 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002554 case SkPath::kConic_Verb:
2555 fCurrConicWeight += 1;
2556 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002557 case SkPath::kQuad_Verb:
2558 ptCount += 2;
2559 break;
2560 case SkPath::kCubic_Verb:
2561 ptCount += 3;
2562 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002563 case SkPath::kClose_Verb:
2564 break;
2565 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002566 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002567 break;
2568 }
2569 }
2570CONTOUR_END:
2571 fCurrPtCount = ptCount;
2572 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002573 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002574}
2575
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002576// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002577static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002578 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2579 // We may get 0 when the above subtracts underflow. We expect this to be
2580 // very rare and lazily promote to double.
2581 if (0 == cross) {
2582 double p0x = SkScalarToDouble(p0.fX);
2583 double p0y = SkScalarToDouble(p0.fY);
2584
2585 double p1x = SkScalarToDouble(p1.fX);
2586 double p1y = SkScalarToDouble(p1.fY);
2587
2588 double p2x = SkScalarToDouble(p2.fX);
2589 double p2y = SkScalarToDouble(p2.fY);
2590
2591 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2592 (p1y - p0y) * (p2x - p0x));
2593
2594 }
2595 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002596}
2597
reed@google.comc1ea60a2012-01-31 15:15:36 +00002598// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002599static int find_max_y(const SkPoint pts[], int count) {
2600 SkASSERT(count > 0);
2601 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002602 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002603 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002604 SkScalar y = pts[i].fY;
2605 if (y > max) {
2606 max = y;
2607 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002608 }
2609 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002610 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002611}
2612
reed@google.comcabaf1d2012-01-11 21:03:05 +00002613static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2614 int i = index;
2615 for (;;) {
2616 i = (i + inc) % n;
2617 if (i == index) { // we wrapped around, so abort
2618 break;
2619 }
2620 if (pts[index] != pts[i]) { // found a different point, success!
2621 break;
2622 }
2623 }
2624 return i;
2625}
2626
reed@google.comc1ea60a2012-01-31 15:15:36 +00002627/**
2628 * Starting at index, and moving forward (incrementing), find the xmin and
2629 * xmax of the contiguous points that have the same Y.
2630 */
2631static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2632 int* maxIndexPtr) {
2633 const SkScalar y = pts[index].fY;
2634 SkScalar min = pts[index].fX;
2635 SkScalar max = min;
2636 int minIndex = index;
2637 int maxIndex = index;
2638 for (int i = index + 1; i < n; ++i) {
2639 if (pts[i].fY != y) {
2640 break;
2641 }
2642 SkScalar x = pts[i].fX;
2643 if (x < min) {
2644 min = x;
2645 minIndex = i;
2646 } else if (x > max) {
2647 max = x;
2648 maxIndex = i;
2649 }
2650 }
2651 *maxIndexPtr = maxIndex;
2652 return minIndex;
2653}
2654
reed026beb52015-06-10 14:23:15 -07002655static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2656 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002657}
2658
reed@google.comac8543f2012-01-30 20:51:25 +00002659/*
2660 * We loop through all contours, and keep the computed cross-product of the
2661 * contour that contained the global y-max. If we just look at the first
2662 * contour, we may find one that is wound the opposite way (correctly) since
2663 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2664 * that is outer most (or at least has the global y-max) before we can consider
2665 * its cross product.
2666 */
reed026beb52015-06-10 14:23:15 -07002667bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002668 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2669 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002670 return true;
2671 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002672
2673 // don't want to pay the cost for computing this if it
2674 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002675 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2676 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002677 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002678 return false;
2679 }
reed@google.com69a99432012-01-10 18:00:10 +00002680
reed026beb52015-06-10 14:23:15 -07002681 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002682
reed@google.comac8543f2012-01-30 20:51:25 +00002683 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002684 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002685 SkScalar ymaxCross = 0;
2686
reed@google.com69a99432012-01-10 18:00:10 +00002687 for (; !iter.done(); iter.next()) {
2688 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002689 if (n < 3) {
2690 continue;
2691 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002692
reed@google.comcabaf1d2012-01-11 21:03:05 +00002693 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002694 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002695 int index = find_max_y(pts, n);
2696 if (pts[index].fY < ymax) {
2697 continue;
2698 }
2699
2700 // If there is more than 1 distinct point at the y-max, we take the
2701 // x-min and x-max of them and just subtract to compute the dir.
2702 if (pts[(index + 1) % n].fY == pts[index].fY) {
2703 int maxIndex;
2704 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2705 if (minIndex == maxIndex) {
2706 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002707 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002708 SkASSERT(pts[minIndex].fY == pts[index].fY);
2709 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2710 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2711 // we just subtract the indices, and let that auto-convert to
2712 // SkScalar, since we just want - or + to signal the direction.
2713 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002714 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002715 TRY_CROSSPROD:
2716 // Find a next and prev index to use for the cross-product test,
2717 // but we try to find pts that form non-zero vectors from pts[index]
2718 //
2719 // Its possible that we can't find two non-degenerate vectors, so
2720 // we have to guard our search (e.g. all the pts could be in the
2721 // same place).
2722
2723 // we pass n - 1 instead of -1 so we don't foul up % operator by
2724 // passing it a negative LH argument.
2725 int prev = find_diff_pt(pts, index, n, n - 1);
2726 if (prev == index) {
2727 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002728 continue;
2729 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002730 int next = find_diff_pt(pts, index, n, 1);
2731 SkASSERT(next != index);
2732 cross = cross_prod(pts[prev], pts[index], pts[next]);
2733 // if we get a zero and the points are horizontal, then we look at the spread in
2734 // x-direction. We really should continue to walk away from the degeneracy until
2735 // there is a divergence.
2736 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2737 // construct the subtract so we get the correct Direction below
2738 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002739 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002740 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002741
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002742 if (cross) {
2743 // record our best guess so far
2744 ymax = pts[index].fY;
2745 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002746 }
2747 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002748 if (ymaxCross) {
2749 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002750 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002751 return true;
2752 } else {
2753 return false;
2754 }
reed@google.comac8543f2012-01-30 20:51:25 +00002755}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002756
2757///////////////////////////////////////////////////////////////////////////////
2758
caryclark9aacd902015-12-14 08:38:09 -08002759static bool between(SkScalar a, SkScalar b, SkScalar c) {
2760 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2761 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2762 return (a - b) * (c - b) <= 0;
2763}
2764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2766 SkScalar t) {
2767 SkScalar A = c3 + 3*(c1 - c2) - c0;
2768 SkScalar B = 3*(c2 - c1 - c1 + c0);
2769 SkScalar C = 3*(c1 - c0);
2770 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002771 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002772}
2773
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002774template <size_t N> static void find_minmax(const SkPoint pts[],
2775 SkScalar* minPtr, SkScalar* maxPtr) {
2776 SkScalar min, max;
2777 min = max = pts[0].fX;
2778 for (size_t i = 1; i < N; ++i) {
2779 min = SkMinScalar(min, pts[i].fX);
2780 max = SkMaxScalar(max, pts[i].fX);
2781 }
2782 *minPtr = min;
2783 *maxPtr = max;
2784}
2785
caryclark9cb5d752015-12-18 04:35:24 -08002786static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2787 if (start.fY == end.fY) {
2788 return between(start.fX, x, end.fX) && x != end.fX;
2789 } else {
2790 return x == start.fX && y == start.fY;
2791 }
2792}
2793
caryclark9aacd902015-12-14 08:38:09 -08002794static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002795 SkScalar y0 = pts[0].fY;
2796 SkScalar y3 = pts[3].fY;
2797
2798 int dir = 1;
2799 if (y0 > y3) {
2800 SkTSwap(y0, y3);
2801 dir = -1;
2802 }
2803 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 return 0;
2805 }
caryclark9cb5d752015-12-18 04:35:24 -08002806 if (checkOnCurve(x, y, pts[0], pts[3])) {
2807 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002808 return 0;
2809 }
caryclark9cb5d752015-12-18 04:35:24 -08002810 if (y == y3) {
2811 return 0;
2812 }
2813
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002814 // quickreject or quickaccept
2815 SkScalar min, max;
2816 find_minmax<4>(pts, &min, &max);
2817 if (x < min) {
2818 return 0;
2819 }
2820 if (x > max) {
2821 return dir;
2822 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002825 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002826 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002827 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002828 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002829 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002830 if (SkScalarNearlyEqual(xt, x)) {
2831 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2832 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002833 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002834 }
2835 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 return xt < x ? dir : 0;
2837}
2838
caryclark9aacd902015-12-14 08:38:09 -08002839static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 SkPoint dst[10];
2841 int n = SkChopCubicAtYExtrema(pts, dst);
2842 int w = 0;
2843 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002844 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 }
2846 return w;
2847}
2848
caryclark9aacd902015-12-14 08:38:09 -08002849static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2850 SkASSERT(src);
2851 SkASSERT(t >= 0 && t <= 1);
2852 SkScalar src2w = src[2] * w;
2853 SkScalar C = src[0];
2854 SkScalar A = src[4] - 2 * src2w + C;
2855 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002856 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002857}
2858
2859
2860static double conic_eval_denominator(SkScalar w, SkScalar t) {
2861 SkScalar B = 2 * (w - 1);
2862 SkScalar C = 1;
2863 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002864 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002865}
2866
2867static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2868 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002869 SkScalar y0 = pts[0].fY;
2870 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002871
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002872 int dir = 1;
2873 if (y0 > y2) {
2874 SkTSwap(y0, y2);
2875 dir = -1;
2876 }
caryclark9aacd902015-12-14 08:38:09 -08002877 if (y < y0 || y > y2) {
2878 return 0;
2879 }
caryclark9cb5d752015-12-18 04:35:24 -08002880 if (checkOnCurve(x, y, pts[0], pts[2])) {
2881 *onCurveCount += 1;
2882 return 0;
2883 }
caryclark9aacd902015-12-14 08:38:09 -08002884 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 return 0;
2886 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002887
caryclark9aacd902015-12-14 08:38:09 -08002888 SkScalar roots[2];
2889 SkScalar A = pts[2].fY;
2890 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2891 SkScalar C = pts[0].fY;
2892 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2893 B -= C; // B = b*w - w * yCept + yCept - a
2894 C -= y;
2895 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2896 SkASSERT(n <= 1);
2897 SkScalar xt;
2898 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002899 // zero roots are returned only when y0 == y
2900 // Need [0] if dir == 1
2901 // and [2] if dir == -1
2902 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002903 } else {
2904 SkScalar t = roots[0];
2905 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2906 }
2907 if (SkScalarNearlyEqual(xt, x)) {
2908 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2909 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002910 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002911 }
2912 }
2913 return xt < x ? dir : 0;
2914}
2915
2916static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2917 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2918 if (y0 == y1) {
2919 return true;
2920 }
2921 if (y0 < y1) {
2922 return y1 <= y2;
2923 } else {
2924 return y1 >= y2;
2925 }
2926}
2927
2928static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2929 int* onCurveCount) {
2930 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002931 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002932 // If the data points are very large, the conic may not be monotonic but may also
2933 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002934 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2935 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2936 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002937 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2938 }
2939 return w;
2940}
2941
2942static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2943 SkScalar y0 = pts[0].fY;
2944 SkScalar y2 = pts[2].fY;
2945
2946 int dir = 1;
2947 if (y0 > y2) {
2948 SkTSwap(y0, y2);
2949 dir = -1;
2950 }
2951 if (y < y0 || y > y2) {
2952 return 0;
2953 }
caryclark9cb5d752015-12-18 04:35:24 -08002954 if (checkOnCurve(x, y, pts[0], pts[2])) {
2955 *onCurveCount += 1;
2956 return 0;
2957 }
caryclark9aacd902015-12-14 08:38:09 -08002958 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002959 return 0;
2960 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002961 // bounds check on X (not required. is it faster?)
2962#if 0
2963 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2964 return 0;
2965 }
2966#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002967
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002968 SkScalar roots[2];
2969 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2970 2 * (pts[1].fY - pts[0].fY),
2971 pts[0].fY - y,
2972 roots);
2973 SkASSERT(n <= 1);
2974 SkScalar xt;
2975 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002976 // zero roots are returned only when y0 == y
2977 // Need [0] if dir == 1
2978 // and [2] if dir == -1
2979 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002980 } else {
2981 SkScalar t = roots[0];
2982 SkScalar C = pts[0].fX;
2983 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2984 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002985 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 }
caryclark9aacd902015-12-14 08:38:09 -08002987 if (SkScalarNearlyEqual(xt, x)) {
2988 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2989 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002990 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002991 }
2992 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 return xt < x ? dir : 0;
2994}
2995
caryclark9aacd902015-12-14 08:38:09 -08002996static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 SkPoint dst[5];
2998 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002999
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3001 n = SkChopQuadAtYExtrema(pts, dst);
3002 pts = dst;
3003 }
caryclark9aacd902015-12-14 08:38:09 -08003004 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003006 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 }
3008 return w;
3009}
3010
caryclark9aacd902015-12-14 08:38:09 -08003011static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 SkScalar x0 = pts[0].fX;
3013 SkScalar y0 = pts[0].fY;
3014 SkScalar x1 = pts[1].fX;
3015 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003016
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003017 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003018
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003019 int dir = 1;
3020 if (y0 > y1) {
3021 SkTSwap(y0, y1);
3022 dir = -1;
3023 }
caryclark9aacd902015-12-14 08:38:09 -08003024 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 return 0;
3026 }
caryclark9cb5d752015-12-18 04:35:24 -08003027 if (checkOnCurve(x, y, pts[0], pts[1])) {
3028 *onCurveCount += 1;
3029 return 0;
3030 }
3031 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003032 return 0;
3033 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003034 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003035
caryclark9aacd902015-12-14 08:38:09 -08003036 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003037 // zero cross means the point is on the line, and since the case where
3038 // y of the query point is at the end point is handled above, we can be
3039 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003040 if (x != x1 || y != pts[1].fY) {
3041 *onCurveCount += 1;
3042 }
caryclark9aacd902015-12-14 08:38:09 -08003043 dir = 0;
3044 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003045 dir = 0;
3046 }
3047 return dir;
3048}
3049
caryclark9aacd902015-12-14 08:38:09 -08003050static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3051 SkTDArray<SkVector>* tangents) {
3052 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3053 && !between(pts[2].fY, y, pts[3].fY)) {
3054 return;
3055 }
3056 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3057 && !between(pts[2].fX, x, pts[3].fX)) {
3058 return;
3059 }
3060 SkPoint dst[10];
3061 int n = SkChopCubicAtYExtrema(pts, dst);
3062 for (int i = 0; i <= n; ++i) {
3063 SkPoint* c = &dst[i * 3];
3064 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003065 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003066 continue;
mbarbella276e6332016-05-31 14:44:01 -07003067 }
caryclark9aacd902015-12-14 08:38:09 -08003068 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3069 if (!SkScalarNearlyEqual(x, xt)) {
3070 continue;
3071 }
3072 SkVector tangent;
3073 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3074 tangents->push(tangent);
3075 }
3076}
3077
3078static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3079 SkTDArray<SkVector>* tangents) {
3080 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3081 return;
3082 }
3083 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3084 return;
3085 }
3086 SkScalar roots[2];
3087 SkScalar A = pts[2].fY;
3088 SkScalar B = pts[1].fY * w - y * w + y;
3089 SkScalar C = pts[0].fY;
3090 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3091 B -= C; // B = b*w - w * yCept + yCept - a
3092 C -= y;
3093 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3094 for (int index = 0; index < n; ++index) {
3095 SkScalar t = roots[index];
3096 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3097 if (!SkScalarNearlyEqual(x, xt)) {
3098 continue;
3099 }
3100 SkConic conic(pts, w);
3101 tangents->push(conic.evalTangentAt(t));
3102 }
3103}
3104
3105static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3106 SkTDArray<SkVector>* tangents) {
3107 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3108 return;
3109 }
3110 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3111 return;
3112 }
3113 SkScalar roots[2];
3114 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3115 2 * (pts[1].fY - pts[0].fY),
3116 pts[0].fY - y,
3117 roots);
3118 for (int index = 0; index < n; ++index) {
3119 SkScalar t = roots[index];
3120 SkScalar C = pts[0].fX;
3121 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3122 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003123 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003124 if (!SkScalarNearlyEqual(x, xt)) {
3125 continue;
3126 }
3127 tangents->push(SkEvalQuadTangentAt(pts, t));
3128 }
3129}
3130
3131static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3132 SkTDArray<SkVector>* tangents) {
3133 SkScalar y0 = pts[0].fY;
3134 SkScalar y1 = pts[1].fY;
3135 if (!between(y0, y, y1)) {
3136 return;
3137 }
3138 SkScalar x0 = pts[0].fX;
3139 SkScalar x1 = pts[1].fX;
3140 if (!between(x0, x, x1)) {
3141 return;
3142 }
3143 SkScalar dx = x1 - x0;
3144 SkScalar dy = y1 - y0;
3145 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3146 return;
3147 }
3148 SkVector v;
3149 v.set(dx, dy);
3150 tangents->push(v);
3151}
3152
reed@google.com4db592c2013-10-30 17:39:43 +00003153static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3154 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3155}
3156
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003157bool SkPath::contains(SkScalar x, SkScalar y) const {
3158 bool isInverse = this->isInverseFillType();
3159 if (this->isEmpty()) {
3160 return isInverse;
3161 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003162
reed@google.com4db592c2013-10-30 17:39:43 +00003163 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003164 return isInverse;
3165 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003166
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003167 SkPath::Iter iter(*this, true);
3168 bool done = false;
3169 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003170 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 do {
3172 SkPoint pts[4];
3173 switch (iter.next(pts, false)) {
3174 case SkPath::kMove_Verb:
3175 case SkPath::kClose_Verb:
3176 break;
3177 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003178 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003179 break;
3180 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003181 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003182 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003183 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003184 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003185 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003186 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003187 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 break;
3189 case SkPath::kDone_Verb:
3190 done = true;
3191 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003192 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003193 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003194 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3195 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3196 if (evenOddFill) {
3197 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003198 }
caryclark9aacd902015-12-14 08:38:09 -08003199 if (w) {
3200 return !isInverse;
3201 }
3202 if (onCurveCount <= 1) {
3203 return SkToBool(onCurveCount) ^ isInverse;
3204 }
3205 if ((onCurveCount & 1) || evenOddFill) {
3206 return SkToBool(onCurveCount & 1) ^ isInverse;
3207 }
halcanary9d524f22016-03-29 09:03:52 -07003208 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003209 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3210 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003211 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003212 SkTDArray<SkVector> tangents;
3213 do {
3214 SkPoint pts[4];
3215 int oldCount = tangents.count();
3216 switch (iter.next(pts, false)) {
3217 case SkPath::kMove_Verb:
3218 case SkPath::kClose_Verb:
3219 break;
3220 case SkPath::kLine_Verb:
3221 tangent_line(pts, x, y, &tangents);
3222 break;
3223 case SkPath::kQuad_Verb:
3224 tangent_quad(pts, x, y, &tangents);
3225 break;
3226 case SkPath::kConic_Verb:
3227 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3228 break;
3229 case SkPath::kCubic_Verb:
3230 tangent_cubic(pts, x, y, &tangents);
3231 break;
3232 case SkPath::kDone_Verb:
3233 done = true;
3234 break;
3235 }
3236 if (tangents.count() > oldCount) {
3237 int last = tangents.count() - 1;
3238 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003239 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003240 tangents.remove(last);
3241 } else {
3242 for (int index = 0; index < last; ++index) {
3243 const SkVector& test = tangents[index];
3244 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003245 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3246 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003247 tangents.remove(last);
3248 tangents.removeShuffle(index);
3249 break;
3250 }
3251 }
3252 }
3253 }
3254 } while (!done);
3255 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003256}
fmalitaaa0df4e2015-12-01 09:13:23 -08003257
3258int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3259 SkScalar w, SkPoint pts[], int pow2) {
3260 const SkConic conic(p0, p1, p2, w);
3261 return conic.chopIntoQuadsPOW2(pts, pow2);
3262}
bsalomonedc743a2016-06-01 09:42:31 -07003263
3264bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3265 unsigned* start) {
3266 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3267 return false;
3268 }
3269 SkPath::RawIter iter(path);
3270 SkPoint verbPts[4];
3271 SkPath::Verb v;
3272 SkPoint rectPts[5];
3273 int rectPtCnt = 0;
3274 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3275 switch (v) {
3276 case SkPath::kMove_Verb:
3277 if (0 != rectPtCnt) {
3278 return false;
3279 }
3280 rectPts[0] = verbPts[0];
3281 ++rectPtCnt;
3282 break;
3283 case SkPath::kLine_Verb:
3284 if (5 == rectPtCnt) {
3285 return false;
3286 }
3287 rectPts[rectPtCnt] = verbPts[1];
3288 ++rectPtCnt;
3289 break;
3290 case SkPath::kClose_Verb:
3291 if (4 == rectPtCnt) {
3292 rectPts[4] = rectPts[0];
3293 rectPtCnt = 5;
3294 }
3295 break;
3296 default:
3297 return false;
3298 }
3299 }
3300 if (rectPtCnt < 5) {
3301 return false;
3302 }
3303 if (rectPts[0] != rectPts[4]) {
3304 return false;
3305 }
bsalomon057ae8a2016-07-24 05:37:26 -07003306 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3307 // and pts 1 and 2 the opposite vertical or horizontal edge).
3308 bool vec03IsVertical;
3309 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3310 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3311 // Make sure it has non-zero width and height
3312 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003313 return false;
3314 }
bsalomon057ae8a2016-07-24 05:37:26 -07003315 vec03IsVertical = true;
3316 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3317 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3318 // Make sure it has non-zero width and height
3319 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3320 return false;
3321 }
3322 vec03IsVertical = false;
3323 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003324 return false;
3325 }
bsalomon057ae8a2016-07-24 05:37:26 -07003326 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3327 // set if it is on the bottom edge.
3328 unsigned sortFlags =
3329 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3330 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3331 switch (sortFlags) {
3332 case 0b00:
3333 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3334 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3335 *start = 0;
3336 break;
3337 case 0b01:
3338 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3339 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3340 *start = 1;
3341 break;
3342 case 0b10:
3343 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3344 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3345 *start = 3;
3346 break;
3347 case 0b11:
3348 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3349 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3350 *start = 2;
3351 break;
bsalomonedc743a2016-06-01 09:42:31 -07003352 }
bsalomonedc743a2016-06-01 09:42:31 -07003353 return true;
3354}
bsalomon21af9ca2016-08-25 12:29:23 -07003355
Brian Salomone4949402018-04-26 15:22:04 -04003356bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3357 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3358 // This gets converted to an oval.
3359 return true;
3360 }
3361 if (useCenter) {
3362 // This is a pie wedge. It's convex if the angle is <= 180.
3363 return SkScalarAbs(sweepAngle) <= 180.f;
3364 }
3365 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3366 // to a secant, i.e. convex.
3367 return SkScalarAbs(sweepAngle) <= 360.f;
3368}
3369
bsalomon21af9ca2016-08-25 12:29:23 -07003370void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3371 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3372 SkASSERT(!oval.isEmpty());
3373 SkASSERT(sweepAngle);
3374
3375 path->reset();
3376 path->setIsVolatile(true);
3377 path->setFillType(SkPath::kWinding_FillType);
3378 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3379 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003380 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003381 return;
3382 }
3383 if (useCenter) {
3384 path->moveTo(oval.centerX(), oval.centerY());
3385 }
Brian Salomone4949402018-04-26 15:22:04 -04003386 auto firstDir =
3387 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3388 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003389 // Arc to mods at 360 and drawArc is not supposed to.
3390 bool forceMoveTo = !useCenter;
3391 while (sweepAngle <= -360.f) {
3392 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3393 startAngle -= 180.f;
3394 path->arcTo(oval, startAngle, -180.f, false);
3395 startAngle -= 180.f;
3396 forceMoveTo = false;
3397 sweepAngle += 360.f;
3398 }
3399 while (sweepAngle >= 360.f) {
3400 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3401 startAngle += 180.f;
3402 path->arcTo(oval, startAngle, 180.f, false);
3403 startAngle += 180.f;
3404 forceMoveTo = false;
3405 sweepAngle -= 360.f;
3406 }
3407 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3408 if (useCenter) {
3409 path->close();
3410 }
Brian Salomone4949402018-04-26 15:22:04 -04003411 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3412 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003413}
Mike Reed0d7dac82017-02-02 17:45:56 -08003414
3415///////////////////////////////////////////////////////////////////////////////////////////////////
3416#include "SkNx.h"
3417
3418static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3419 SkScalar ts[2];
3420 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3421 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3422 SkASSERT(n >= 0 && n <= 2);
3423 for (int i = 0; i < n; ++i) {
3424 extremas[i] = SkEvalQuadAt(src, ts[i]);
3425 }
3426 extremas[n] = src[2];
3427 return n + 1;
3428}
3429
3430static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3431 SkConic conic(src[0], src[1], src[2], w);
3432 SkScalar ts[2];
3433 int n = conic.findXExtrema(ts);
3434 n += conic.findYExtrema(&ts[n]);
3435 SkASSERT(n >= 0 && n <= 2);
3436 for (int i = 0; i < n; ++i) {
3437 extremas[i] = conic.evalAt(ts[i]);
3438 }
3439 extremas[n] = src[2];
3440 return n + 1;
3441}
3442
3443static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3444 SkScalar ts[4];
3445 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3446 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3447 SkASSERT(n >= 0 && n <= 4);
3448 for (int i = 0; i < n; ++i) {
3449 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3450 }
3451 extremas[n] = src[3];
3452 return n + 1;
3453}
3454
Mike Reed8d3196b2017-02-03 11:34:13 -05003455SkRect SkPath::computeTightBounds() const {
3456 if (0 == this->countVerbs()) {
3457 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003458 }
3459
Mike Reed8d3196b2017-02-03 11:34:13 -05003460 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3461 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003462 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003463
Mike Reed0d7dac82017-02-02 17:45:56 -08003464 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3465 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003466 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003467
3468 // initial with the first MoveTo, so we don't have to check inside the switch
3469 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003470 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003471 for (;;) {
3472 int count = 0;
3473 switch (iter.next(pts)) {
3474 case SkPath::kMove_Verb:
3475 extremas[0] = pts[0];
3476 count = 1;
3477 break;
3478 case SkPath::kLine_Verb:
3479 extremas[0] = pts[1];
3480 count = 1;
3481 break;
3482 case SkPath::kQuad_Verb:
3483 count = compute_quad_extremas(pts, extremas);
3484 break;
3485 case SkPath::kConic_Verb:
3486 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3487 break;
3488 case SkPath::kCubic_Verb:
3489 count = compute_cubic_extremas(pts, extremas);
3490 break;
3491 case SkPath::kClose_Verb:
3492 break;
3493 case SkPath::kDone_Verb:
3494 goto DONE;
3495 }
3496 for (int i = 0; i < count; ++i) {
3497 Sk2s tmp = from_point(extremas[i]);
3498 min = Sk2s::Min(min, tmp);
3499 max = Sk2s::Max(max, tmp);
3500 }
3501 }
3502DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003503 SkRect bounds;
3504 min.store((SkPoint*)&bounds.fLeft);
3505 max.store((SkPoint*)&bounds.fRight);
3506 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003507}
Cary Clarkdf429f32017-11-08 11:44:31 -05003508
3509bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3510 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3511}
3512
3513bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3514 const SkPoint& p3, bool exact) {
3515 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3516 SkPointPriv::EqualsWithinTolerance(p2, p3);
3517}
3518
3519bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3520 const SkPoint& p3, const SkPoint& p4, bool exact) {
3521 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3522 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3523 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3524 SkPointPriv::EqualsWithinTolerance(p3, p4);
3525}