blob: 09d0a9523fbf0e82e95a533feb0e0c2b82d7ef1c [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"
Mike Reedc3d8a482018-09-12 10:08:40 -040022#include "SkTLazy.h"
Hal Canaryc640d0d2018-06-13 09:59:02 -040023#include "SkTo.h"
24
25#include <cmath>
Ben Wagnerf08d1d02018-06-18 15:11:00 -040026#include <utility>
reed@android.com8a1c16f2008-12-17 15:59:43 +000027
Mike Reeda99b6ce2017-02-04 11:04:26 -050028static float poly_eval(float A, float B, float C, float t) {
29 return (A * t + B) * t + C;
30}
31
32static float poly_eval(float A, float B, float C, float D, float t) {
33 return ((A * t + B) * t + C) * t + D;
34}
35
reed@android.com8a1c16f2008-12-17 15:59:43 +000036////////////////////////////////////////////////////////////////////////////
37
reed@google.com3563c9e2011-11-14 19:34:57 +000038/**
39 * Path.bounds is defined to be the bounds of all the control points.
40 * If we called bounds.join(r) we would skip r if r was empty, which breaks
41 * our promise. Hence we have a custom joiner that doesn't look at emptiness
42 */
43static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
44 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
45 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
46 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
47 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
48}
49
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000050static bool is_degenerate(const SkPath& path) {
51 SkPath::Iter iter(path, false);
52 SkPoint pts[4];
53 return SkPath::kDone_Verb == iter.next(pts);
54}
55
bsalomon@google.com30c174b2012-11-13 14:36:42 +000056class SkAutoDisableDirectionCheck {
57public:
58 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070059 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060 }
61
62 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070063 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064 }
65
66private:
reed026beb52015-06-10 14:23:15 -070067 SkPath* fPath;
68 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000069};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000070#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000071
reed@android.com8a1c16f2008-12-17 15:59:43 +000072/* This guy's constructor/destructor bracket a path editing operation. It is
73 used when we know the bounds of the amount we are going to add to the path
74 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000075
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000077 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000078 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000079
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000080 It also notes if the path was originally degenerate, and if so, sets
81 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000082 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 */
84class SkAutoPathBoundsUpdate {
85public:
86 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
87 this->init(path);
88 }
89
90 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
91 SkScalar right, SkScalar bottom) {
92 fRect.set(left, top, right, bottom);
93 this->init(path);
94 }
reed@google.comabf15c12011-01-18 20:35:51 +000095
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000097 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
98 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050099 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 }
102 }
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000105 SkPath* fPath;
106 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000107 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000108 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000109 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000110
reed@android.com6b82d1a2009-06-03 02:35:01 +0000111 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 // Cannot use fRect for our bounds unless we know it is sorted
113 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000114 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000115 // Mark the path's bounds as dirty if (1) they are, or (2) the path
116 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000117 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000118 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000119 if (fHasValidBounds && !fEmpty) {
120 joinNoEmptyChecks(&fRect, fPath->getBounds());
121 }
122 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123 }
124};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000125#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126
reed@android.com8a1c16f2008-12-17 15:59:43 +0000127////////////////////////////////////////////////////////////////////////////
128
129/*
130 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000131 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
133
134 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000135 1. If we encounter degenerate segments, remove them
136 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
137 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
138 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139*/
140
141////////////////////////////////////////////////////////////////////////////
142
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000143// flag to require a moveTo if we begin with something else, like lineTo etc.
144#define INITIAL_LASTMOVETOINDEX_VALUE ~0
145
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000146SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800147 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700149 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400150 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000151}
152
153void SkPath::resetFields() {
154 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000155 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000157 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700158 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000159
160 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700161 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000162}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000165 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166 this->copyFields(that);
167 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168}
169
170SkPath::~SkPath() {
171 SkDEBUGCODE(this->validate();)
172}
173
bungeman@google.coma5809a32013-06-21 15:13:34 +0000174SkPath& SkPath::operator=(const SkPath& that) {
175 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000176
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177 if (this != &that) {
178 fPathRef.reset(SkRef(that.fPathRef.get()));
179 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180 }
181 SkDEBUGCODE(this->validate();)
182 return *this;
183}
184
bungeman@google.coma5809a32013-06-21 15:13:34 +0000185void SkPath::copyFields(const SkPath& that) {
186 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000187 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700189 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400190 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400191
192 // Non-atomic assignment of atomic values.
193 fConvexity .store(that.fConvexity .load());
194 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195}
196
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000197bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000198 // note: don't need to look at isConvex or bounds, since just comparing the
199 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000200 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000201 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000202}
203
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205 if (this != &that) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400206 using std::swap;
bungeman77a53de2015-10-01 12:28:49 -0700207 fPathRef.swap(that.fPathRef);
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400208 swap(fLastMoveToIndex, that.fLastMoveToIndex);
209 swap(fFillType, that.fFillType);
210 swap(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400211
212 // Non-atomic swaps of atomic values.
213 Convexity c = fConvexity.load();
214 fConvexity.store(that.fConvexity.load());
215 that.fConvexity.store(c);
216
217 uint8_t fd = fFirstDirection.load();
218 fFirstDirection.store(that.fFirstDirection.load());
219 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000220 }
221}
222
caryclark8e7b19d2016-02-18 04:11:48 -0800223bool SkPath::isInterpolatable(const SkPath& compare) const {
224 int count = fPathRef->countVerbs();
225 if (count != compare.fPathRef->countVerbs()) {
226 return false;
227 }
228 if (!count) {
229 return true;
230 }
231 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
232 count)) {
233 return false;
234 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800235 return !fPathRef->countWeights() ||
236 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800237 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
238}
239
240bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500241 int pointCount = fPathRef->countPoints();
242 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return false;
244 }
Cary Clark7487ec82018-03-06 09:30:46 -0500245 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800246 return true;
247 }
caryclark8e7b19d2016-02-18 04:11:48 -0800248 out->reset();
249 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700250 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800251 return true;
252}
253
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000254static inline bool check_edge_against_rect(const SkPoint& p0,
255 const SkPoint& p1,
256 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700257 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000258 const SkPoint* edgeBegin;
259 SkVector v;
reed026beb52015-06-10 14:23:15 -0700260 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000261 v = p1 - p0;
262 edgeBegin = &p0;
263 } else {
264 v = p0 - p1;
265 edgeBegin = &p1;
266 }
267 if (v.fX || v.fY) {
268 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500269 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
270 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
271 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
272 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000273 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
274 return false;
275 }
276 }
277 return true;
278}
279
280bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
281 // This only handles non-degenerate convex paths currently.
282 if (kConvex_Convexity != this->getConvexity()) {
283 return false;
284 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000285
reed026beb52015-06-10 14:23:15 -0700286 SkPathPriv::FirstDirection direction;
287 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 return false;
289 }
290
291 SkPoint firstPt;
292 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500293 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000294 SkPath::Verb verb;
295 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400296 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000299
Lee Salzmana19f0242017-01-12 13:06:21 -0500300 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000301 int nextPt = -1;
302 switch (verb) {
303 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000304 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000305 SkDEBUGCODE(++moveCnt);
306 firstPt = prevPt = pts[0];
307 break;
308 case kLine_Verb:
309 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000310 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400311 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000312 break;
313 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000314 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000315 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400316 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000317 nextPt = 2;
318 break;
319 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000320 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400321 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000322 nextPt = 3;
323 break;
324 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000325 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000326 break;
327 default:
328 SkDEBUGFAIL("unknown verb");
329 }
330 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800331 if (SkPath::kConic_Verb == verb) {
332 SkConic orig;
333 orig.set(pts, iter.conicWeight());
334 SkPoint quadPts[5];
335 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800336 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800337
338 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
339 return false;
340 }
341 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
342 return false;
343 }
344 } else {
345 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
346 return false;
347 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000348 }
349 prevPt = pts[nextPt];
350 }
351 }
352
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400353 if (segmentCount) {
354 return check_edge_against_rect(prevPt, firstPt, rect, direction);
355 }
356 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000357}
358
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000359uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000360 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800361#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400362 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
363 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000364#endif
365 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000366}
djsollen@google.come63793a2012-03-21 15:39:03 +0000367
Mike Reedb6317422018-08-15 10:23:39 -0400368SkPath& SkPath::reset() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000369 SkDEBUGCODE(this->validate();)
370
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000371 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000372 this->resetFields();
Mike Reedb6317422018-08-15 10:23:39 -0400373 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374}
375
Mike Reedb6317422018-08-15 10:23:39 -0400376SkPath& SkPath::rewind() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000377 SkDEBUGCODE(this->validate();)
378
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000379 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000380 this->resetFields();
Mike Reedb6317422018-08-15 10:23:39 -0400381 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000382}
383
fsb1475b02016-01-20 09:51:07 -0800384bool SkPath::isLastContourClosed() const {
385 int verbCount = fPathRef->countVerbs();
386 if (0 == verbCount) {
387 return false;
388 }
389 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
390}
391
reed@google.com7e6c4d12012-05-10 14:05:43 +0000392bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000393 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000394
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000395 if (2 == verbCount) {
396 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
397 if (kLine_Verb == fPathRef->atVerb(1)) {
398 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000399 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000400 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000401 line[0] = pts[0];
402 line[1] = pts[1];
403 }
404 return true;
405 }
406 }
407 return false;
408}
409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410/*
411 Determines if path is a rect by keeping track of changes in direction
412 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000413
caryclark@google.comf1316942011-07-26 19:54:45 +0000414 The direction is computed such that:
415 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000416 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000417 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000418 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000419
caryclark@google.comf1316942011-07-26 19:54:45 +0000420A rectangle cycles up/right/down/left or up/left/down/right.
421
422The test fails if:
423 The path is closed, and followed by a line.
424 A second move creates a new endpoint.
425 A diagonal line is parsed.
426 There's more than four changes of direction.
427 There's a discontinuity on the line (e.g., a move in the middle)
428 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 The path contains a quadratic or cubic.
430 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000431 *The rectangle doesn't complete a cycle.
432 *The final point isn't equal to the first point.
433
434 *These last two conditions we relax if we have a 3-edge path that would
435 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000436
caryclark@google.comf1316942011-07-26 19:54:45 +0000437It's OK if the path has:
438 Several colinear line segments composing a rectangle side.
439 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000440
caryclark@google.comf1316942011-07-26 19:54:45 +0000441The direction takes advantage of the corners found since opposite sides
442must travel in opposite directions.
443
444FIXME: Allow colinear quads and cubics to be treated like lines.
445FIXME: If the API passes fill-only, return true if the filled stroke
446 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000447
Cary Clark48c464a2018-04-16 12:06:07 -0400448 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000449 0x1 is set if the segment is horizontal
450 0x2 is set if the segment is moving to the right or down
451 thus:
452 two directions are opposites iff (dirA ^ dirB) == 0x2
453 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000454
caryclark@google.comf1316942011-07-26 19:54:45 +0000455 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000456static int rect_make_dir(SkScalar dx, SkScalar dy) {
457 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
458}
Cary Clark88ba9712018-04-12 14:00:24 -0400459
caryclark@google.comf68154a2012-11-21 15:18:06 +0000460bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400461 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000462 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400463 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400464 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400465 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
466 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400467 SkPoint firstCorner;
468 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000469 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400470 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400471 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400472 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000473 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700475 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000476 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000477 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700478 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
479 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000480 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000481 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700483 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400485 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400486 lastPt = pts;
487 }
Cary Clark88ba9712018-04-12 14:00:24 -0400488 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
489 SkVector lineDelta = lineEnd - lineStart;
490 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 return false; // diagonal
492 }
Cary Clark1eece782018-04-20 10:14:41 -0400493 if (!lineDelta.isFinite()) {
494 return false; // path contains infinity or NaN
495 }
Cary Clark88ba9712018-04-12 14:00:24 -0400496 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000497 break; // single point on side OK
498 }
Cary Clark48c464a2018-04-16 12:06:07 -0400499 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000500 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400501 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000502 corners = 1;
503 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400504 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000505 break;
506 }
507 if (closedOrMoved) {
508 return false; // closed followed by a line
509 }
Cary Clark48c464a2018-04-16 12:06:07 -0400510 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000511 break; // colinear with first
512 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000513 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400514 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400515 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400516 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400517 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400518 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 break; // colinear segment
520 }
Cary Clark48c464a2018-04-16 12:06:07 -0400521 directions[corners++] = nextDirection;
522 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400523 switch (corners) {
524 case 2:
525 firstCorner = lineStart;
526 break;
527 case 3:
528 if ((directions[0] ^ directions[2]) != 2) {
529 return false;
530 }
531 thirdCorner = lineEnd;
532 break;
533 case 4:
534 if ((directions[1] ^ directions[3]) != 2) {
535 return false;
536 }
537 break;
538 default:
539 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000540 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400541 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000542 break;
543 }
544 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000545 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000546 case kCubic_Verb:
547 return false; // quadratic, cubic not allowed
548 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400549 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700550 insertClose = true;
551 *currVerb -= 1; // try move again afterwards
552 goto addMissingClose;
553 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400554 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400555 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400556 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400557 closeXY = *firstPt - *lastPt;
558 if (closeXY.fX && closeXY.fY) {
559 return false; // we're diagonal, abort
560 }
Cary Clark8540e112018-04-11 14:30:27 -0400561 }
Cary Clark88ba9712018-04-12 14:00:24 -0400562 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000563 closedOrMoved = true;
564 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000565 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000566 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000567 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000568 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000569 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700570addMissingClose:
571 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000572 }
573 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400574 if (corners < 3 || corners > 4) {
575 return false;
576 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000577 if (savePts) {
578 *ptsPtr = savePts;
579 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400580 // check if close generates diagonal
581 closeXY = *firstPt - *lastPt;
582 if (closeXY.fX && closeXY.fY) {
583 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400584 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400585 if (rect) {
586 rect->set(firstCorner, thirdCorner);
587 }
588 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000589 *isClosed = autoClose;
590 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400591 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400592 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000593 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400594 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595}
596
robertphillips4f662e62014-12-29 14:06:51 -0800597bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000598 SkDEBUGCODE(this->validate();)
599 int currVerb = 0;
600 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400601 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000602}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000603
caryclark95bc5f32015-04-08 08:34:15 -0700604bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000605 SkDEBUGCODE(this->validate();)
606 int currVerb = 0;
607 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000608 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400609 SkRect testRects[2];
610 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000611 return false;
612 }
Cary Clark5c715182018-04-09 16:07:11 -0400613 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000614 if (testRects[0].contains(testRects[1])) {
615 if (rects) {
616 rects[0] = testRects[0];
617 rects[1] = testRects[1];
618 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000619 if (dirs) {
620 dirs[0] = testDirs[0];
621 dirs[1] = testDirs[1];
622 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000623 return true;
624 }
625 if (testRects[1].contains(testRects[0])) {
626 if (rects) {
627 rects[0] = testRects[1];
628 rects[1] = testRects[0];
629 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000630 if (dirs) {
631 dirs[0] = testDirs[1];
632 dirs[1] = testDirs[0];
633 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000634 return true;
635 }
636 }
637 return false;
638}
639
Mike Reed0c3137c2018-02-20 13:57:05 -0500640bool SkPath::isOval(SkRect* bounds) const {
641 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
642}
643
644bool SkPath::isRRect(SkRRect* rrect) const {
645 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
646}
647
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000648int SkPath::countPoints() const {
649 return fPathRef->countPoints();
650}
651
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000652int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653 SkDEBUGCODE(this->validate();)
654
655 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000656 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000657 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800658 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000660}
661
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000662SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
664 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000665 }
666 return SkPoint::Make(0, 0);
667}
668
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000669int SkPath::countVerbs() const {
670 return fPathRef->countVerbs();
671}
672
673static inline void copy_verbs_reverse(uint8_t* inorderDst,
674 const uint8_t* reversedSrc,
675 int count) {
676 for (int i = 0; i < count; ++i) {
677 inorderDst[i] = reversedSrc[~i];
678 }
679}
680
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000681int SkPath::getVerbs(uint8_t dst[], int max) const {
682 SkDEBUGCODE(this->validate();)
683
684 SkASSERT(max >= 0);
685 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000686 int count = SkMin32(max, fPathRef->countVerbs());
687 copy_verbs_reverse(dst, fPathRef->verbs(), count);
688 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000689}
690
reed@google.com294dd7b2011-10-11 11:58:32 +0000691bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 SkDEBUGCODE(this->validate();)
693
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000694 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000695 if (count > 0) {
696 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000697 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000699 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000701 if (lastPt) {
702 lastPt->set(0, 0);
703 }
704 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705}
706
caryclarkaec25102015-04-29 08:28:30 -0700707void SkPath::setPt(int index, SkScalar x, SkScalar y) {
708 SkDEBUGCODE(this->validate();)
709
710 int count = fPathRef->countPoints();
711 if (count <= index) {
712 return;
713 } else {
714 SkPathRef::Editor ed(&fPathRef);
715 ed.atPoint(index)->set(x, y);
716 }
717}
718
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719void SkPath::setLastPt(SkScalar x, SkScalar y) {
720 SkDEBUGCODE(this->validate();)
721
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000722 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 if (count == 0) {
724 this->moveTo(x, y);
725 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000726 SkPathRef::Editor ed(&fPathRef);
727 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728 }
729}
730
reed@google.com04863fa2011-05-15 04:08:24 +0000731void SkPath::setConvexity(Convexity c) {
732 if (fConvexity != c) {
733 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000734 }
735}
736
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737//////////////////////////////////////////////////////////////////////////////
738// Construction methods
739
reed026beb52015-06-10 14:23:15 -0700740#define DIRTY_AFTER_EDIT \
741 do { \
742 fConvexity = kUnknown_Convexity; \
743 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000744 } while (0)
745
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746void SkPath::incReserve(U16CPU inc) {
747 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000748 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000749 SkDEBUGCODE(this->validate();)
750}
751
Mike Reedb6317422018-08-15 10:23:39 -0400752SkPath& SkPath::moveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753 SkDEBUGCODE(this->validate();)
754
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000757 // remember our index
758 fLastMoveToIndex = fPathRef->countPoints();
759
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000760 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700761
762 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400763 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000764}
765
Mike Reedb6317422018-08-15 10:23:39 -0400766SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 SkPoint pt;
768 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400769 return this->moveTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770}
771
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000772void SkPath::injectMoveToIfNeeded() {
773 if (fLastMoveToIndex < 0) {
774 SkScalar x, y;
775 if (fPathRef->countVerbs() == 0) {
776 x = y = 0;
777 } else {
778 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
779 x = pt.fX;
780 y = pt.fY;
781 }
782 this->moveTo(x, y);
783 }
784}
785
Mike Reedb6317422018-08-15 10:23:39 -0400786SkPath& SkPath::lineTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 SkDEBUGCODE(this->validate();)
788
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000789 this->injectMoveToIfNeeded();
790
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000791 SkPathRef::Editor ed(&fPathRef);
792 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793
reed@google.comb54455e2011-05-16 14:16:04 +0000794 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400795 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796}
797
Mike Reedb6317422018-08-15 10:23:39 -0400798SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000799 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 SkPoint pt;
801 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400802 return this->lineTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803}
804
Mike Reedb6317422018-08-15 10:23:39 -0400805SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000807
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000808 this->injectMoveToIfNeeded();
809
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000810 SkPathRef::Editor ed(&fPathRef);
811 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 pts[0].set(x1, y1);
813 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000814
reed@google.comb54455e2011-05-16 14:16:04 +0000815 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400816 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817}
818
Mike Reedb6317422018-08-15 10:23:39 -0400819SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000820 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821 SkPoint pt;
822 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400823 return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000824}
825
Mike Reedb6317422018-08-15 10:23:39 -0400826SkPath& SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
827 SkScalar w) {
reed@google.com277c3f82013-05-31 15:17:50 +0000828 // check for <= 0 or NaN with this test
829 if (!(w > 0)) {
830 this->lineTo(x2, y2);
831 } else if (!SkScalarIsFinite(w)) {
832 this->lineTo(x1, y1);
833 this->lineTo(x2, y2);
834 } else if (SK_Scalar1 == w) {
835 this->quadTo(x1, y1, x2, y2);
836 } else {
837 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000838
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000839 this->injectMoveToIfNeeded();
840
reed@google.com277c3f82013-05-31 15:17:50 +0000841 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000842 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000843 pts[0].set(x1, y1);
844 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000845
reed@google.com277c3f82013-05-31 15:17:50 +0000846 DIRTY_AFTER_EDIT;
847 }
Mike Reedb6317422018-08-15 10:23:39 -0400848 return *this;
reed@google.com277c3f82013-05-31 15:17:50 +0000849}
850
Mike Reedb6317422018-08-15 10:23:39 -0400851SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
852 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000853 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000854 SkPoint pt;
855 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400856 return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000857}
858
Mike Reedb6317422018-08-15 10:23:39 -0400859SkPath& SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
860 SkScalar x3, SkScalar y3) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861 SkDEBUGCODE(this->validate();)
862
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000863 this->injectMoveToIfNeeded();
864
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000865 SkPathRef::Editor ed(&fPathRef);
866 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 pts[0].set(x1, y1);
868 pts[1].set(x2, y2);
869 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870
reed@google.comb54455e2011-05-16 14:16:04 +0000871 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400872 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873}
874
Mike Reedb6317422018-08-15 10:23:39 -0400875SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
876 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000877 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 SkPoint pt;
879 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400880 return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
881 pt.fX + x3, pt.fY + y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882}
883
Mike Reedb6317422018-08-15 10:23:39 -0400884SkPath& SkPath::close() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000885 SkDEBUGCODE(this->validate();)
886
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000889 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 case kLine_Verb:
891 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000892 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000893 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000894 case kMove_Verb: {
895 SkPathRef::Editor ed(&fPathRef);
896 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000898 }
reed@google.com277c3f82013-05-31 15:17:50 +0000899 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000900 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000901 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000902 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000903 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000904 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000905 }
906 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000907
908 // signal that we need a moveTo to follow us (unless we're done)
909#if 0
910 if (fLastMoveToIndex >= 0) {
911 fLastMoveToIndex = ~fLastMoveToIndex;
912 }
913#else
914 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
915#endif
Mike Reedb6317422018-08-15 10:23:39 -0400916 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000917}
918
919///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000920
fmalitac08d53e2015-11-17 09:53:29 -0800921namespace {
922
923template <unsigned N>
924class PointIterator {
925public:
926 PointIterator(SkPath::Direction dir, unsigned startIndex)
927 : fCurrent(startIndex % N)
928 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
929
930 const SkPoint& current() const {
931 SkASSERT(fCurrent < N);
932 return fPts[fCurrent];
933 }
934
935 const SkPoint& next() {
936 fCurrent = (fCurrent + fAdvance) % N;
937 return this->current();
938 }
939
940protected:
941 SkPoint fPts[N];
942
943private:
944 unsigned fCurrent;
945 unsigned fAdvance;
946};
947
948class RectPointIterator : public PointIterator<4> {
949public:
950 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
951 : PointIterator(dir, startIndex) {
952
953 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
954 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
955 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
956 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
957 }
958};
959
960class OvalPointIterator : public PointIterator<4> {
961public:
962 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
963 : PointIterator(dir, startIndex) {
964
965 const SkScalar cx = oval.centerX();
966 const SkScalar cy = oval.centerY();
967
968 fPts[0] = SkPoint::Make(cx, oval.fTop);
969 fPts[1] = SkPoint::Make(oval.fRight, cy);
970 fPts[2] = SkPoint::Make(cx, oval.fBottom);
971 fPts[3] = SkPoint::Make(oval.fLeft, cy);
972 }
973};
974
975class RRectPointIterator : public PointIterator<8> {
976public:
977 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
978 : PointIterator(dir, startIndex) {
979
980 const SkRect& bounds = rrect.getBounds();
981 const SkScalar L = bounds.fLeft;
982 const SkScalar T = bounds.fTop;
983 const SkScalar R = bounds.fRight;
984 const SkScalar B = bounds.fBottom;
985
986 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
987 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
988 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
989 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
990 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
991 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
992 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
993 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
994 }
995};
996
997} // anonymous namespace
998
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000999static void assert_known_direction(int dir) {
1000 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
1001}
1002
Mike Reedb6317422018-08-15 10:23:39 -04001003SkPath& SkPath::addRect(const SkRect& rect, Direction dir) {
1004 return this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005}
1006
Mike Reedb6317422018-08-15 10:23:39 -04001007SkPath& SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008 SkScalar bottom, Direction dir) {
Mike Reedb6317422018-08-15 10:23:39 -04001009 return this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
fmalitac08d53e2015-11-17 09:53:29 -08001010}
1011
Mike Reedb6317422018-08-15 10:23:39 -04001012SkPath& SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001013 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001014 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001015 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001016 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001017 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001018
fmalitac08d53e2015-11-17 09:53:29 -08001019 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020
fmalitac08d53e2015-11-17 09:53:29 -08001021 const int kVerbs = 5; // moveTo + 3x lineTo + close
1022 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023
fmalitac08d53e2015-11-17 09:53:29 -08001024 RectPointIterator iter(rect, dir, startIndex);
1025
1026 this->moveTo(iter.current());
1027 this->lineTo(iter.next());
1028 this->lineTo(iter.next());
1029 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001030 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001031
1032 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
Mike Reedb6317422018-08-15 10:23:39 -04001033 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034}
1035
Mike Reedb6317422018-08-15 10:23:39 -04001036SkPath& SkPath::addPoly(const SkPoint pts[], int count, bool close) {
reed@google.com744faba2012-05-29 19:54:52 +00001037 SkDEBUGCODE(this->validate();)
1038 if (count <= 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001039 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001040 }
1041
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001042 fLastMoveToIndex = fPathRef->countPoints();
1043
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001044 // +close makes room for the extra kClose_Verb
1045 SkPathRef::Editor ed(&fPathRef, count+close, count);
1046
1047 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001048 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001049 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1050 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001051 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001052
reed@google.com744faba2012-05-29 19:54:52 +00001053 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001054 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001055 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001056 }
1057
reed@google.com744faba2012-05-29 19:54:52 +00001058 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001059 SkDEBUGCODE(this->validate();)
Mike Reedb6317422018-08-15 10:23:39 -04001060 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001061}
1062
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001063#include "SkGeometry.h"
1064
reedf90ea012015-01-29 12:03:58 -08001065static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1066 SkPoint* pt) {
1067 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001068 // Chrome uses this path to move into and out of ovals. If not
1069 // treated as a special case the moves can distort the oval's
1070 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001071 pt->set(oval.fRight, oval.centerY());
1072 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001073 } else if (0 == oval.width() && 0 == oval.height()) {
1074 // Chrome will sometimes create 0 radius round rects. Having degenerate
1075 // quad segments in the path prevents the path from being recognized as
1076 // a rect.
1077 // TODO: optimizing the case where only one of width or height is zero
1078 // should also be considered. This case, however, doesn't seem to be
1079 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001080 pt->set(oval.fRight, oval.fTop);
1081 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001082 }
reedf90ea012015-01-29 12:03:58 -08001083 return false;
1084}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001085
reedd5d27d92015-02-09 13:54:43 -08001086// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1087//
1088static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1089 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1090 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1091 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001092
1093 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001094 loss in radians-conversion and/or sin/cos, we may end up with coincident
1095 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1096 of drawing a nearly complete circle (good).
1097 e.g. canvas.drawArc(0, 359.99, ...)
1098 -vs- canvas.drawArc(0, 359.9, ...)
1099 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001100 */
reedd5d27d92015-02-09 13:54:43 -08001101 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001102 SkScalar sw = SkScalarAbs(sweepAngle);
1103 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1104 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1105 // make a guess at a tiny angle (in radians) to tweak by
1106 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1107 // not sure how much will be enough, so we use a loop
1108 do {
1109 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001110 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1111 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001112 }
1113 }
reedd5d27d92015-02-09 13:54:43 -08001114 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1115}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001116
reed9e779d42015-02-17 11:43:14 -08001117/**
1118 * If this returns 0, then the caller should just line-to the singlePt, else it should
1119 * ignore singlePt and append the specified number of conics.
1120 */
reedd5d27d92015-02-09 13:54:43 -08001121static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001122 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1123 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001124 SkMatrix matrix;
1125
1126 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1127 matrix.postTranslate(oval.centerX(), oval.centerY());
1128
reed9e779d42015-02-17 11:43:14 -08001129 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1130 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001131 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001132 }
1133 return count;
reedd5d27d92015-02-09 13:54:43 -08001134}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001135
Mike Reedb6317422018-08-15 10:23:39 -04001136SkPath& SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001137 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001138 SkRRect rrect;
1139 rrect.setRectRadii(rect, (const SkVector*) radii);
Mike Reedb6317422018-08-15 10:23:39 -04001140 return this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001141}
1142
Mike Reedb6317422018-08-15 10:23:39 -04001143SkPath& SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001144 // legacy start indices: 6 (CW) and 7(CCW)
Mike Reedb6317422018-08-15 10:23:39 -04001145 return this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
fmalitac08d53e2015-11-17 09:53:29 -08001146}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001147
Mike Reedb6317422018-08-15 10:23:39 -04001148SkPath& SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1149 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001150
Mike Reedb6317422018-08-15 10:23:39 -04001151 bool isRRect = hasOnlyMoveTos();
1152 const SkRect& bounds = rrect.getBounds();
fmalitac08d53e2015-11-17 09:53:29 -08001153
Mike Reedb6317422018-08-15 10:23:39 -04001154 if (rrect.isRect() || rrect.isEmpty()) {
1155 // degenerate(rect) => radii points are collapsing
1156 this->addRect(bounds, dir, (startIndex + 1) / 2);
1157 } else if (rrect.isOval()) {
1158 // degenerate(oval) => line points are collapsing
1159 this->addOval(bounds, dir, startIndex / 2);
1160 } else {
1161 fFirstDirection = this->hasOnlyMoveTos() ?
1162 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
fmalitac08d53e2015-11-17 09:53:29 -08001163
Mike Reedb6317422018-08-15 10:23:39 -04001164 SkAutoPathBoundsUpdate apbu(this, bounds);
1165 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001166
Mike Reedb6317422018-08-15 10:23:39 -04001167 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1168 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1169 const SkScalar weight = SK_ScalarRoot2Over2;
fmalitac08d53e2015-11-17 09:53:29 -08001170
Mike Reedb6317422018-08-15 10:23:39 -04001171 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1172 const int kVerbs = startsWithConic
1173 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1174 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1175 this->incReserve(kVerbs);
fmalitac08d53e2015-11-17 09:53:29 -08001176
Mike Reedb6317422018-08-15 10:23:39 -04001177 RRectPointIterator rrectIter(rrect, dir, startIndex);
1178 // Corner iterator indices follow the collapsed radii model,
1179 // adjusted such that the start pt is "behind" the radii start pt.
1180 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1181 RectPointIterator rectIter(bounds, dir, rectStartIndex);
fmalitac08d53e2015-11-17 09:53:29 -08001182
Mike Reedb6317422018-08-15 10:23:39 -04001183 this->moveTo(rrectIter.current());
1184 if (startsWithConic) {
1185 for (unsigned i = 0; i < 3; ++i) {
fmalitac08d53e2015-11-17 09:53:29 -08001186 this->conicTo(rectIter.next(), rrectIter.next(), weight);
Mike Reedb6317422018-08-15 10:23:39 -04001187 this->lineTo(rrectIter.next());
fmalitac08d53e2015-11-17 09:53:29 -08001188 }
Mike Reedb6317422018-08-15 10:23:39 -04001189 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1190 // final lineTo handled by close().
1191 } else {
1192 for (unsigned i = 0; i < 4; ++i) {
1193 this->lineTo(rrectIter.next());
1194 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1195 }
fmalitac08d53e2015-11-17 09:53:29 -08001196 }
Mike Reedb6317422018-08-15 10:23:39 -04001197 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001198
Mike Reedb6317422018-08-15 10:23:39 -04001199 SkPathRef::Editor ed(&fPathRef);
1200 ed.setIsRRect(isRRect, dir, startIndex % 8);
1201
1202 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1203 }
1204
1205 SkDEBUGCODE(fPathRef->validate();)
1206 return *this;
reed@google.com4ed0fb72012-12-12 20:48:18 +00001207}
1208
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001210 int count = fPathRef->countVerbs();
1211 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1212 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001213 if (*verbs == kLine_Verb ||
1214 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001215 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001216 *verbs == kCubic_Verb) {
1217 return false;
1218 }
1219 ++verbs;
1220 }
1221 return true;
1222}
1223
Brian Osmana2318572017-07-10 15:09:26 -04001224bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1225 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001226 if (count < 2) {
1227 return true;
1228 }
Brian Osmana2318572017-07-10 15:09:26 -04001229 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001230 const SkPoint& first = *pts;
1231 for (int index = 1; index < count; ++index) {
1232 if (first != pts[index]) {
1233 return false;
1234 }
1235 }
1236 return true;
1237}
1238
Mike Reedb6317422018-08-15 10:23:39 -04001239SkPath& SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1240 Direction dir) {
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001241 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001242
humper@google.com75e3ca12013-04-08 21:44:11 +00001243 if (rx < 0 || ry < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001244 return *this;
humper@google.com75e3ca12013-04-08 21:44:11 +00001245 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001246
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001247 SkRRect rrect;
1248 rrect.setRectXY(rect, rx, ry);
Mike Reedb6317422018-08-15 10:23:39 -04001249 return this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001250}
1251
Mike Reedb6317422018-08-15 10:23:39 -04001252SkPath& SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001253 // legacy start index: 1
Mike Reedb6317422018-08-15 10:23:39 -04001254 return this->addOval(oval, dir, 1);
fmalitac08d53e2015-11-17 09:53:29 -08001255}
1256
Mike Reedb6317422018-08-15 10:23:39 -04001257SkPath& SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001258 assert_known_direction(dir);
1259
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001260 /* If addOval() is called after previous moveTo(),
1261 this path is still marked as an oval. This is used to
1262 fit into WebKit's calling sequences.
1263 We can't simply check isEmpty() in this case, as additional
1264 moveTo() would mark the path non empty.
1265 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001266 bool isOval = hasOnlyMoveTos();
1267 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001268 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001269 } else {
reed026beb52015-06-10 14:23:15 -07001270 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001271 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001272
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001273 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001274 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275
fmalitac08d53e2015-11-17 09:53:29 -08001276 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1277 const int kVerbs = 6; // moveTo + 4x conicTo + close
1278 this->incReserve(kVerbs);
1279
1280 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1281 // The corner iterator pts are tracking "behind" the oval/radii pts.
1282 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001283 const SkScalar weight = SK_ScalarRoot2Over2;
1284
fmalitac08d53e2015-11-17 09:53:29 -08001285 this->moveTo(ovalIter.current());
1286 for (unsigned i = 0; i < 4; ++i) {
1287 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001288 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290
fmalitac08d53e2015-11-17 09:53:29 -08001291 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1292
robertphillips@google.com466310d2013-12-03 16:43:54 +00001293 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001294
bsalomon78d58d12016-05-27 09:17:04 -07001295 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
Mike Reedb6317422018-08-15 10:23:39 -04001296 return *this;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001297}
1298
Mike Reedb6317422018-08-15 10:23:39 -04001299SkPath& SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001301 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001302 }
Mike Reedb6317422018-08-15 10:23:39 -04001303 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304}
1305
Mike Reedb6317422018-08-15 10:23:39 -04001306SkPath& SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1307 bool forceMoveTo) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001308 if (oval.width() < 0 || oval.height() < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001309 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 }
1311
reedf90ea012015-01-29 12:03:58 -08001312 if (fPathRef->countVerbs() == 0) {
1313 forceMoveTo = true;
1314 }
1315
1316 SkPoint lonePt;
1317 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
Mike Reedb6317422018-08-15 10:23:39 -04001318 return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
reedf90ea012015-01-29 12:03:58 -08001319 }
1320
reedd5d27d92015-02-09 13:54:43 -08001321 SkVector startV, stopV;
1322 SkRotationDirection dir;
1323 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1324
reed9e779d42015-02-17 11:43:14 -08001325 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001326
Brian Salomone4949402018-04-26 15:22:04 -04001327 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1328 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1329 // arcs from the same oval.
1330 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1331 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001332 if (forceMoveTo) {
1333 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001334 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001335 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1336 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1337 this->lineTo(pt);
1338 }
1339 };
1340
xidachen6069dda2016-10-06 05:42:23 -07001341 // At this point, we know that the arc is not a lone point, but startV == stopV
1342 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1343 // cannot handle it.
1344 if (startV == stopV) {
1345 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1346 SkScalar radiusX = oval.width() / 2;
1347 SkScalar radiusY = oval.height() / 2;
1348 // We cannot use SkScalarSinCos function in the next line because
1349 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1350 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001351 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001352 // make sin(endAngle) to be 0 which will then draw a dot.
1353 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1354 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001355 addPt(singlePt);
Mike Reedb6317422018-08-15 10:23:39 -04001356 return *this;
xidachen6069dda2016-10-06 05:42:23 -07001357 }
1358
reedd5d27d92015-02-09 13:54:43 -08001359 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001360 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001361 if (count) {
1362 this->incReserve(count * 2 + 1);
1363 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001364 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001365 for (int i = 0; i < count; ++i) {
1366 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1367 }
reed9e779d42015-02-17 11:43:14 -08001368 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001369 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001370 }
Mike Reedb6317422018-08-15 10:23:39 -04001371 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372}
1373
caryclark55d49052016-01-23 05:07:04 -08001374// This converts the SVG arc to conics.
1375// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1376// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1377// See also SVG implementation notes:
1378// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1379// Note that arcSweep bool value is flipped from the original implementation.
Mike Reedb6317422018-08-15 10:23:39 -04001380SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1381 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001382 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001383 SkPoint srcPts[2];
1384 this->getLastPt(&srcPts[0]);
1385 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1386 // joining the endpoints.
1387 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1388 if (!rx || !ry) {
Mike Reedb6317422018-08-15 10:23:39 -04001389 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001390 }
1391 // If the current point and target point for the arc are identical, it should be treated as a
1392 // zero length path. This ensures continuity in animations.
1393 srcPts[1].set(x, y);
1394 if (srcPts[0] == srcPts[1]) {
Mike Reedb6317422018-08-15 10:23:39 -04001395 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001396 }
1397 rx = SkScalarAbs(rx);
1398 ry = SkScalarAbs(ry);
1399 SkVector midPointDistance = srcPts[0] - srcPts[1];
1400 midPointDistance *= 0.5f;
1401
1402 SkMatrix pointTransform;
1403 pointTransform.setRotate(-angle);
1404
1405 SkPoint transformedMidPoint;
1406 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1407 SkScalar squareRx = rx * rx;
1408 SkScalar squareRy = ry * ry;
1409 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1410 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1411
1412 // Check if the radii are big enough to draw the arc, scale radii if not.
1413 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1414 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1415 if (radiiScale > 1) {
1416 radiiScale = SkScalarSqrt(radiiScale);
1417 rx *= radiiScale;
1418 ry *= radiiScale;
1419 }
1420
1421 pointTransform.setScale(1 / rx, 1 / ry);
1422 pointTransform.preRotate(-angle);
1423
1424 SkPoint unitPts[2];
1425 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1426 SkVector delta = unitPts[1] - unitPts[0];
1427
1428 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1429 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1430
1431 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1432 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1433 scaleFactor = -scaleFactor;
1434 }
1435 delta.scale(scaleFactor);
1436 SkPoint centerPoint = unitPts[0] + unitPts[1];
1437 centerPoint *= 0.5f;
1438 centerPoint.offset(-delta.fY, delta.fX);
1439 unitPts[0] -= centerPoint;
1440 unitPts[1] -= centerPoint;
1441 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1442 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1443 SkScalar thetaArc = theta2 - theta1;
1444 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1445 thetaArc += SK_ScalarPI * 2;
1446 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1447 thetaArc -= SK_ScalarPI * 2;
1448 }
1449 pointTransform.setRotate(angle);
1450 pointTransform.preScale(rx, ry);
1451
Cary Clark1acd3c72017-12-08 11:37:01 -05001452#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001453 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001454#else
1455 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1456 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1457#endif
caryclark55d49052016-01-23 05:07:04 -08001458 SkScalar thetaWidth = thetaArc / segments;
1459 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1460 if (!SkScalarIsFinite(t)) {
Mike Reedb6317422018-08-15 10:23:39 -04001461 return *this;
caryclark55d49052016-01-23 05:07:04 -08001462 }
1463 SkScalar startTheta = theta1;
1464 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001465#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1466 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1467 return scalar == SkScalarFloorToScalar(scalar);
1468 };
1469 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1470 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1471 scalar_is_integer(x) && scalar_is_integer(y);
1472#endif
caryclark55d49052016-01-23 05:07:04 -08001473 for (int i = 0; i < segments; ++i) {
1474 SkScalar endTheta = startTheta + thetaWidth;
1475 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1476
1477 unitPts[1].set(cosEndTheta, sinEndTheta);
1478 unitPts[1] += centerPoint;
1479 unitPts[0] = unitPts[1];
1480 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1481 SkPoint mapped[2];
1482 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001483 /*
1484 Computing the arc width introduces rounding errors that cause arcs to start
1485 outside their marks. A round rect may lose convexity as a result. If the input
1486 values are on integers, place the conic on integers as well.
1487 */
1488#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1489 if (expectIntegers) {
1490 SkScalar* mappedScalars = &mapped[0].fX;
1491 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1492 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1493 }
1494 }
1495#endif
caryclark55d49052016-01-23 05:07:04 -08001496 this->conicTo(mapped[0], mapped[1], w);
1497 startTheta = endTheta;
1498 }
Mike Reedb6317422018-08-15 10:23:39 -04001499 return *this;
caryclark55d49052016-01-23 05:07:04 -08001500}
1501
Mike Reedb6317422018-08-15 10:23:39 -04001502SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1503 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
caryclark55d49052016-01-23 05:07:04 -08001504 SkPoint currentPoint;
1505 this->getLastPt(&currentPoint);
Mike Reedb6317422018-08-15 10:23:39 -04001506 return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
1507 currentPoint.fX + dx, currentPoint.fY + dy);
caryclark55d49052016-01-23 05:07:04 -08001508}
1509
Mike Reedb6317422018-08-15 10:23:39 -04001510SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 if (oval.isEmpty() || 0 == sweepAngle) {
Mike Reedb6317422018-08-15 10:23:39 -04001512 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 }
1514
1515 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1516
1517 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001518 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1519 // See SkPath::addOval() docs.
1520 SkScalar startOver90 = startAngle / 90.f;
1521 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1522 SkScalar error = startOver90 - startOver90I;
1523 if (SkScalarNearlyEqual(error, 0)) {
1524 // Index 1 is at startAngle == 0.
1525 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1526 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
Mike Reedb6317422018-08-15 10:23:39 -04001527 return this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1528 (unsigned) startIndex);
bsalomon1978ce22016-05-31 10:42:16 -07001529 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 }
Mike Reedb6317422018-08-15 10:23:39 -04001531 return this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532}
1533
1534/*
1535 Need to handle the case when the angle is sharp, and our computed end-points
1536 for the arc go behind pt1 and/or p2...
1537*/
Mike Reedb6317422018-08-15 10:23:39 -04001538SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001539 if (radius == 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001540 return this->lineTo(x1, y1);
reeda8b326c2014-12-09 11:50:32 -08001541 }
1542
1543 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001544
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545 // need to know our prev pt so we can construct tangent vectors
1546 {
1547 SkPoint start;
1548 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001549 // Handle degenerate cases by adding a line to the first point and
1550 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551 before.setNormalize(x1 - start.fX, y1 - start.fY);
1552 after.setNormalize(x2 - x1, y2 - y1);
1553 }
reed@google.comabf15c12011-01-18 20:35:51 +00001554
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 SkScalar cosh = SkPoint::DotProduct(before, after);
1556 SkScalar sinh = SkPoint::CrossProduct(before, after);
1557
1558 if (SkScalarNearlyZero(sinh)) { // angle is too tight
Mike Reedb6317422018-08-15 10:23:39 -04001559 return this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 }
reed@google.comabf15c12011-01-18 20:35:51 +00001561
Mike Reeda99b6ce2017-02-04 11:04:26 -05001562 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563
Mike Reeda99b6ce2017-02-04 11:04:26 -05001564 SkScalar xx = x1 - dist * before.fX;
1565 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001566 after.setLength(dist);
1567 this->lineTo(xx, yy);
1568 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
Mike Reedb6317422018-08-15 10:23:39 -04001569 return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570}
1571
1572///////////////////////////////////////////////////////////////////////////////
1573
Mike Reedb6317422018-08-15 10:23:39 -04001574SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001575 SkMatrix matrix;
1576
1577 matrix.setTranslate(dx, dy);
Mike Reedb6317422018-08-15 10:23:39 -04001578 return this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579}
1580
Mike Reedc3d8a482018-09-12 10:08:40 -04001581SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) {
1582 // Detect if we're trying to add ourself
1583 const SkPath* src = &srcPath;
1584 SkTLazy<SkPath> tmp;
1585 if (this == src) {
1586 src = tmp.set(srcPath);
1587 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588
Mike Reedc3d8a482018-09-12 10:08:40 -04001589 SkPathRef::Editor(&fPathRef, src->countVerbs(), src->countPoints());
1590
1591 RawIter iter(*src);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 SkPoint pts[4];
1593 Verb verb;
1594
Cary Clark9480d822017-10-19 18:01:13 -04001595 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001596 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 while ((verb = iter.next(pts)) != kDone_Verb) {
1598 switch (verb) {
1599 case kMove_Verb:
1600 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001601 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1602 injectMoveToIfNeeded(); // In case last contour is closed
Cary Clark54ff3022018-08-17 10:58:23 -04001603 SkPoint lastPt;
1604 // don't add lineTo if it is degenerate
1605 if (fLastMoveToIndex < 0 || !this->getLastPt(&lastPt) || lastPt != pts[0]) {
1606 this->lineTo(pts[0]);
1607 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001608 } else {
1609 this->moveTo(pts[0]);
1610 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 break;
1612 case kLine_Verb:
1613 proc(matrix, &pts[1], &pts[1], 1);
1614 this->lineTo(pts[1]);
1615 break;
1616 case kQuad_Verb:
1617 proc(matrix, &pts[1], &pts[1], 2);
1618 this->quadTo(pts[1], pts[2]);
1619 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001620 case kConic_Verb:
1621 proc(matrix, &pts[1], &pts[1], 2);
1622 this->conicTo(pts[1], pts[2], iter.conicWeight());
1623 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624 case kCubic_Verb:
1625 proc(matrix, &pts[1], &pts[1], 3);
1626 this->cubicTo(pts[1], pts[2], pts[3]);
1627 break;
1628 case kClose_Verb:
1629 this->close();
1630 break;
1631 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001632 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001634 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 }
Mike Reedb6317422018-08-15 10:23:39 -04001636 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637}
1638
1639///////////////////////////////////////////////////////////////////////////////
1640
reed@google.com277c3f82013-05-31 15:17:50 +00001641static int pts_in_verb(unsigned verb) {
1642 static const uint8_t gPtsInVerb[] = {
1643 1, // kMove
1644 1, // kLine
1645 2, // kQuad
1646 2, // kConic
1647 3, // kCubic
1648 0, // kClose
1649 0 // kDone
1650 };
1651
1652 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1653 return gPtsInVerb[verb];
1654}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655
reed@android.com8a1c16f2008-12-17 15:59:43 +00001656// ignore the last point of the 1st contour
Mike Reedb6317422018-08-15 10:23:39 -04001657SkPath& SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001658 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1659 if (!verbs) { // empty path returns nullptr
Mike Reedb6317422018-08-15 10:23:39 -04001660 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001661 }
caryclark51c56782016-11-07 05:09:28 -08001662 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1663 SkASSERT(verbsEnd[0] == kMove_Verb);
1664 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1665 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666
caryclark51c56782016-11-07 05:09:28 -08001667 while (verbs < verbsEnd) {
1668 uint8_t v = *verbs++;
1669 pts -= pts_in_verb(v);
1670 switch (v) {
1671 case kMove_Verb:
1672 // if the path has multiple contours, stop after reversing the last
Mike Reedb6317422018-08-15 10:23:39 -04001673 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001675 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 break;
1677 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001678 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001680 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001681 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001682 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001684 this->cubicTo(pts[2], pts[1], pts[0]);
1685 break;
1686 case kClose_Verb:
1687 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 break;
1689 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001690 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 break;
1692 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693 }
Mike Reedb6317422018-08-15 10:23:39 -04001694 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695}
1696
Mike Reedb6317422018-08-15 10:23:39 -04001697SkPath& SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001698 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001699
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001700 const SkPoint* pts = src.fPathRef->pointsEnd();
1701 // we will iterator through src's verbs backwards
1702 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1703 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001704 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001705
1706 bool needMove = true;
1707 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001708 while (verbs < verbsEnd) {
1709 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001710 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001711
1712 if (needMove) {
1713 --pts;
1714 this->moveTo(pts->fX, pts->fY);
1715 needMove = false;
1716 }
1717 pts -= n;
1718 switch (v) {
1719 case kMove_Verb:
1720 if (needClose) {
1721 this->close();
1722 needClose = false;
1723 }
1724 needMove = true;
1725 pts += 1; // so we see the point in "if (needMove)" above
1726 break;
1727 case kLine_Verb:
1728 this->lineTo(pts[0]);
1729 break;
1730 case kQuad_Verb:
1731 this->quadTo(pts[1], pts[0]);
1732 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001733 case kConic_Verb:
1734 this->conicTo(pts[1], pts[0], *--conicWeights);
1735 break;
reed@google.com63d73742012-01-10 15:33:12 +00001736 case kCubic_Verb:
1737 this->cubicTo(pts[2], pts[1], pts[0]);
1738 break;
1739 case kClose_Verb:
1740 needClose = true;
1741 break;
1742 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001743 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001744 }
1745 }
Mike Reedb6317422018-08-15 10:23:39 -04001746 return *this;
reed@google.com63d73742012-01-10 15:33:12 +00001747}
1748
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749///////////////////////////////////////////////////////////////////////////////
1750
1751void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1752 SkMatrix matrix;
1753
1754 matrix.setTranslate(dx, dy);
1755 this->transform(matrix, dst);
1756}
1757
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1759 int level = 2) {
1760 if (--level >= 0) {
1761 SkPoint tmp[7];
1762
1763 SkChopCubicAtHalf(pts, tmp);
1764 subdivide_cubic_to(path, &tmp[0], level);
1765 subdivide_cubic_to(path, &tmp[3], level);
1766 } else {
1767 path->cubicTo(pts[1], pts[2], pts[3]);
1768 }
1769}
1770
1771void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1772 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001773 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774 dst = (SkPath*)this;
1775 }
1776
tomhudson@google.com8d430182011-06-06 19:11:19 +00001777 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 SkPath tmp;
1779 tmp.fFillType = fFillType;
1780
1781 SkPath::Iter iter(*this, false);
1782 SkPoint pts[4];
1783 SkPath::Verb verb;
1784
reed@google.com4a3b7142012-05-16 17:16:46 +00001785 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 switch (verb) {
1787 case kMove_Verb:
1788 tmp.moveTo(pts[0]);
1789 break;
1790 case kLine_Verb:
1791 tmp.lineTo(pts[1]);
1792 break;
1793 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001794 // promote the quad to a conic
1795 tmp.conicTo(pts[1], pts[2],
1796 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001797 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001798 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001799 tmp.conicTo(pts[1], pts[2],
1800 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001801 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 case kCubic_Verb:
1803 subdivide_cubic_to(&tmp, pts);
1804 break;
1805 case kClose_Verb:
1806 tmp.close();
1807 break;
1808 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001809 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 break;
1811 }
1812 }
1813
1814 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001815 SkPathRef::Editor ed(&dst->fPathRef);
1816 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001817 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001819 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1820
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001823 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001824 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001826
reed026beb52015-06-10 14:23:15 -07001827 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1828 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001829 } else {
1830 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001831 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1832 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001833 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001834 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1835 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001836 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001837 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001838 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001839 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001840 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001841 }
1842 }
1843
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 SkDEBUGCODE(dst->validate();)
1845 }
1846}
1847
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848///////////////////////////////////////////////////////////////////////////////
1849///////////////////////////////////////////////////////////////////////////////
1850
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851SkPath::Iter::Iter() {
1852#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001853 fPts = nullptr;
1854 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001856 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001857 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001858#endif
1859 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001860 fVerbs = nullptr;
1861 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 fNeedClose = false;
1863}
1864
1865SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1866 this->setPath(path, forceClose);
1867}
1868
1869void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001870 fPts = path.fPathRef->points();
1871 fVerbs = path.fPathRef->verbs();
1872 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001873 fConicWeights = path.fPathRef->conicWeights();
1874 if (fConicWeights) {
1875 fConicWeights -= 1; // begin one behind
1876 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001878 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 fForceClose = SkToU8(forceClose);
1880 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001881 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882}
1883
1884bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001885 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886 return false;
1887 }
1888 if (fForceClose) {
1889 return true;
1890 }
1891
1892 const uint8_t* verbs = fVerbs;
1893 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001894
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001895 if (kMove_Verb == *(verbs - 1)) {
1896 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897 }
1898
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001899 while (verbs > stop) {
1900 // verbs points one beyond the current verb, decrement first.
1901 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 if (kMove_Verb == v) {
1903 break;
1904 }
1905 if (kClose_Verb == v) {
1906 return true;
1907 }
1908 }
1909 return false;
1910}
1911
1912SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001915 // A special case: if both points are NaN, SkPoint::operation== returns
1916 // false, but the iterator expects that they are treated as the same.
1917 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001918 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1919 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001920 return kClose_Verb;
1921 }
1922
reed@google.com9e25dbf2012-05-15 17:05:38 +00001923 pts[0] = fLastPt;
1924 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 fLastPt = fMoveTo;
1926 fCloseLine = true;
1927 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001928 } else {
1929 pts[0] = fMoveTo;
1930 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001932}
1933
reed@google.com9e25dbf2012-05-15 17:05:38 +00001934const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 if (fSegmentState == kAfterMove_SegmentState) {
1936 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001938 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001940
1941 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1942 // Set the first return pt to the last pt of the previous primitive.
1943 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944}
1945
caryclarke8c56662015-07-14 11:19:26 -07001946void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 // We need to step over anything that will not move the current draw point
1948 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001949 const uint8_t* lastMoveVerb = nullptr;
1950 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001951 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 SkPoint lastPt = fLastPt;
1953 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001954 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001955 switch (verb) {
1956 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 // Keep a record of this most recent move
1958 lastMoveVerb = fVerbs;
1959 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001960 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001961 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001962 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 fPts++;
1964 break;
1965
1966 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001967 // A close when we are in a segment is always valid except when it
1968 // follows a move which follows a segment.
1969 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 return;
1971 }
1972 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001973 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 break;
1975
1976 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001977 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 if (lastMoveVerb) {
1979 fVerbs = lastMoveVerb;
1980 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001981 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 return;
1983 }
1984 return;
1985 }
1986 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001987 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 fPts++;
1989 break;
1990
reed@google.com277c3f82013-05-31 15:17:50 +00001991 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001993 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001994 if (lastMoveVerb) {
1995 fVerbs = lastMoveVerb;
1996 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001997 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998 return;
1999 }
2000 return;
2001 }
2002 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002003 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002004 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00002005 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002006 break;
2007
2008 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002009 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002010 if (lastMoveVerb) {
2011 fVerbs = lastMoveVerb;
2012 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002013 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002014 return;
2015 }
2016 return;
2017 }
2018 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002019 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002020 fPts += 3;
2021 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002022
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002023 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002024 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002025 }
2026 }
2027}
2028
reed@google.com4a3b7142012-05-16 17:16:46 +00002029SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002030 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002031
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002033 // Close the curve if requested and if there is some curve to close
2034 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002035 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 return kLine_Verb;
2037 }
2038 fNeedClose = false;
2039 return kClose_Verb;
2040 }
2041 return kDone_Verb;
2042 }
2043
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002044 // fVerbs is one beyond the current verb, decrement first
2045 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002046 const SkPoint* SK_RESTRICT srcPts = fPts;
2047 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048
2049 switch (verb) {
2050 case kMove_Verb:
2051 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002052 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053 verb = this->autoClose(pts);
2054 if (verb == kClose_Verb) {
2055 fNeedClose = false;
2056 }
2057 return (Verb)verb;
2058 }
2059 if (fVerbs == fVerbStop) { // might be a trailing moveto
2060 return kDone_Verb;
2061 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002062 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002063 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002065 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002066 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 fNeedClose = fForceClose;
2068 break;
2069 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002070 pts[0] = this->cons_moveTo();
2071 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 fLastPt = srcPts[0];
2073 fCloseLine = false;
2074 srcPts += 1;
2075 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002076 case kConic_Verb:
2077 fConicWeights += 1;
2078 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002080 pts[0] = this->cons_moveTo();
2081 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 fLastPt = srcPts[1];
2083 srcPts += 2;
2084 break;
2085 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002086 pts[0] = this->cons_moveTo();
2087 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 fLastPt = srcPts[2];
2089 srcPts += 3;
2090 break;
2091 case kClose_Verb:
2092 verb = this->autoClose(pts);
2093 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002094 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002095 } else {
2096 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002097 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002098 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002099 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 break;
2101 }
2102 fPts = srcPts;
2103 return (Verb)verb;
2104}
2105
2106///////////////////////////////////////////////////////////////////////////////
2107
Ben Wagner4d1955c2017-03-10 13:08:15 -05002108#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002109#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002110#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002111
reed@google.com51bbe752013-01-17 22:07:50 +00002112static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002113 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002114 str->append(label);
2115 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002116
reed@google.com51bbe752013-01-17 22:07:50 +00002117 const SkScalar* values = &pts[0].fX;
2118 count *= 2;
2119
2120 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002121 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002122 if (i < count - 1) {
2123 str->append(", ");
2124 }
2125 }
Mike Reed405b9d22018-01-09 15:11:08 -05002126 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002127 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002128 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002129 }
caryclark08fa28c2014-10-23 13:08:56 -07002130 str->append(");");
reede05fed02014-12-15 07:59:53 -08002131 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002132 str->append(" // ");
2133 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002134 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002135 if (i < count - 1) {
2136 str->append(", ");
2137 }
2138 }
2139 if (conicWeight >= 0) {
2140 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002141 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002142 }
2143 }
2144 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002145}
2146
caryclarke9562592014-09-15 09:26:09 -07002147void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002148 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 Iter iter(*this, forceClose);
2150 SkPoint pts[4];
2151 Verb verb;
2152
reed@google.com51bbe752013-01-17 22:07:50 +00002153 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002154 char const * const gFillTypeStrs[] = {
2155 "Winding",
2156 "EvenOdd",
2157 "InverseWinding",
2158 "InverseEvenOdd",
2159 };
2160 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2161 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002162 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 switch (verb) {
2164 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002165 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
2167 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002168 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 break;
2170 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002171 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002173 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002174 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002175 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002176 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002177 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002178 break;
2179 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002180 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002181 break;
2182 default:
2183 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2184 verb = kDone_Verb; // stop the loop
2185 break;
2186 }
caryclark1049f122015-04-20 08:31:59 -07002187 if (!wStream && builder.size()) {
2188 SkDebugf("%s", builder.c_str());
2189 builder.reset();
2190 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002191 }
caryclark66a5d8b2014-06-24 08:30:15 -07002192 if (wStream) {
2193 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002194 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195}
2196
reed@android.come522ca52009-11-23 20:10:41 +00002197void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002198 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002199}
2200
2201void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002202 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002203}
2204
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002205
Cary Clark0461e9f2017-08-25 15:13:38 -04002206bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002207 if ((fFillType & ~3) != 0) {
2208 return false;
2209 }
reed@google.comabf15c12011-01-18 20:35:51 +00002210
djsollen@google.com077348c2012-10-22 20:23:32 +00002211#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002212 if (!fBoundsIsDirty) {
2213 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002214
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002215 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002216 if (SkToBool(fIsFinite) != isFinite) {
2217 return false;
2218 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002219
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002220 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002221 // if we're empty, fBounds may be empty but translated, so we can't
2222 // necessarily compare to bounds directly
2223 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2224 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002225 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2226 return false;
2227 }
reed@android.come522ca52009-11-23 20:10:41 +00002228 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002229 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002230 if (!fBounds.isEmpty()) {
2231 return false;
2232 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002233 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002234 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002235 if (!fBounds.contains(bounds)) {
2236 return false;
2237 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002238 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002239 }
reed@android.come522ca52009-11-23 20:10:41 +00002240 }
2241 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002242#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002243 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002244}
reed@android.come522ca52009-11-23 20:10:41 +00002245
reed@google.com04863fa2011-05-15 04:08:24 +00002246///////////////////////////////////////////////////////////////////////////////
2247
reed@google.com0b7b9822011-05-16 12:29:27 +00002248static int sign(SkScalar x) { return x < 0; }
2249#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002250
robertphillipsc506e302014-09-16 09:43:31 -07002251enum DirChange {
2252 kLeft_DirChange,
2253 kRight_DirChange,
2254 kStraight_DirChange,
2255 kBackwards_DirChange,
2256
2257 kInvalid_DirChange
2258};
2259
2260
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002261static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002262 // The error epsilon was empirically derived; worse case round rects
2263 // with a mid point outset by 2x float epsilon in tests had an error
2264 // of 12.
2265 const int epsilon = 16;
2266 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2267 return false;
2268 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002269 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002270 int aBits = SkFloatAs2sCompliment(compA);
2271 int bBits = SkFloatAs2sCompliment(compB);
2272 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002273}
2274
caryclarkb4216502015-03-02 13:02:34 -08002275static bool approximately_zero_when_compared_to(double x, double y) {
2276 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002277}
2278
caryclarkb4216502015-03-02 13:02:34 -08002279
reed@google.com04863fa2011-05-15 04:08:24 +00002280// only valid for a single contour
2281struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282 Convexicator()
2283 : fPtCount(0)
2284 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002285 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002286 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002287 , fIsCurve(false)
2288 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002289 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002290 // warnings
djsollen2f124632016-04-29 13:53:05 -07002291 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002292 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002293 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002294 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002295 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002296
2297 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002298 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 }
2300
2301 SkPath::Convexity getConvexity() const { return fConvexity; }
2302
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002303 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002304 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002305
reed@google.com04863fa2011-05-15 04:08:24 +00002306 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002307 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002308 return;
2309 }
2310
2311 if (0 == fPtCount) {
2312 fCurrPt = pt;
2313 ++fPtCount;
2314 } else {
2315 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002316 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002317 if (!SkScalarIsFinite(lengthSqd)) {
2318 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002319 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002320 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002321 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002322 fCurrPt = pt;
2323 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002324 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002325 } else {
2326 SkASSERT(fPtCount > 2);
2327 this->addVec(vec);
2328 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002329
reed@google.com85b6e392011-05-15 20:25:17 +00002330 int sx = sign(vec.fX);
2331 int sy = sign(vec.fY);
2332 fDx += (sx != fSx);
2333 fDy += (sy != fSy);
2334 fSx = sx;
2335 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002336
reed@google.com85b6e392011-05-15 20:25:17 +00002337 if (fDx > 3 || fDy > 3) {
2338 fConvexity = SkPath::kConcave_Convexity;
2339 }
reed@google.com04863fa2011-05-15 04:08:24 +00002340 }
2341 }
2342 }
2343
2344 void close() {
2345 if (fPtCount > 2) {
2346 this->addVec(fFirstVec);
2347 }
2348 }
2349
caryclarkb4216502015-03-02 13:02:34 -08002350 DirChange directionChange(const SkVector& curVec) {
2351 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2352
2353 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2354 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2355 largest = SkTMax(largest, -smallest);
2356
2357 if (!almost_equal(largest, largest + cross)) {
2358 int sign = SkScalarSignAsInt(cross);
2359 if (sign) {
2360 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2361 }
2362 }
2363
2364 if (cross) {
2365 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2366 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2367 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2368 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2369 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2370 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2371 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2372 if (sign) {
2373 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2374 }
2375 }
2376 }
2377
Cary Clarkdf429f32017-11-08 11:44:31 -05002378 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2379 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2380 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2381 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002382 fLastVec.dot(curVec) < 0.0f) {
2383 return kBackwards_DirChange;
2384 }
2385
2386 return kStraight_DirChange;
2387 }
2388
Cary Clarkc8323aa2017-08-25 08:04:43 -04002389 bool hasBackwards() const {
2390 return fBackwards;
2391 }
caryclarkb4216502015-03-02 13:02:34 -08002392
caryclarkd3d1a982014-12-08 04:57:38 -08002393 bool isFinite() const {
2394 return fIsFinite;
2395 }
2396
caryclark5ccef572015-03-02 10:07:56 -08002397 void setCurve(bool isCurve) {
2398 fIsCurve = isCurve;
2399 }
2400
reed@google.com04863fa2011-05-15 04:08:24 +00002401private:
2402 void addVec(const SkVector& vec) {
2403 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002404 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002405 switch (dir) {
2406 case kLeft_DirChange: // fall through
2407 case kRight_DirChange:
2408 if (kInvalid_DirChange == fExpectedDir) {
2409 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002410 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2411 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002412 } else if (dir != fExpectedDir) {
2413 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002414 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002415 }
2416 fLastVec = vec;
2417 break;
2418 case kStraight_DirChange:
2419 break;
2420 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002421 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002422 // If any of the subsequent dir is non-backward, it'll be concave.
2423 // Otherwise, it's still convex.
2424 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002425 }
robertphillipsc506e302014-09-16 09:43:31 -07002426 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002427 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002428 break;
2429 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002430 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002431 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002432 }
2433 }
2434
caryclarkb4216502015-03-02 13:02:34 -08002435 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002436 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002437 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002438 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2439 // value with the current vec is deemed to be of a significant value.
2440 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002442 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002443 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002444 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002445 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002446 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002447 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002448 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002449};
2450
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002451SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002452 // Sometimes we think we need to calculate convexity but another thread already did.
2453 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2454 return c;
2455 }
2456
reed@google.com04863fa2011-05-15 04:08:24 +00002457 SkPoint pts[4];
2458 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002459 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002460
2461 int contourCount = 0;
2462 int count;
2463 Convexicator state;
2464
caryclarkd3d1a982014-12-08 04:57:38 -08002465 if (!isFinite()) {
2466 return kUnknown_Convexity;
2467 }
Brian Osman205a1262017-09-18 13:13:48 +00002468 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002469 switch (verb) {
2470 case kMove_Verb:
2471 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002472 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002473 return kConcave_Convexity;
2474 }
2475 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002476 // fall through
2477 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002478 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002479 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002480 break;
caryclark5ccef572015-03-02 10:07:56 -08002481 case kQuad_Verb:
2482 // fall through
2483 case kConic_Verb:
2484 // fall through
2485 case kCubic_Verb:
2486 count = 2 + (kCubic_Verb == verb);
2487 // As an additional enhancement, this could set curve true only
2488 // if the curve is nonlinear
2489 state.setCurve(true);
2490 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002491 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002492 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002493 state.close();
2494 count = 0;
2495 break;
2496 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002497 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002498 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002499 return kConcave_Convexity;
2500 }
2501
2502 for (int i = 1; i <= count; i++) {
2503 state.addPt(pts[i]);
2504 }
2505 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002506 if (!state.isFinite()) {
2507 return kUnknown_Convexity;
2508 }
reed@google.com04863fa2011-05-15 04:08:24 +00002509 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002510 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002511 return kConcave_Convexity;
2512 }
2513 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002514 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002515 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002516 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2517 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2518 fConvexity = Convexity::kConcave_Convexity;
2519 } else {
2520 fFirstDirection = state.getFirstDirection();
2521 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002522 }
2523 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002524}
reed@google.com69a99432012-01-10 18:00:10 +00002525
2526///////////////////////////////////////////////////////////////////////////////
2527
2528class ContourIter {
2529public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002531
2532 bool done() const { return fDone; }
2533 // if !done() then these may be called
2534 int count() const { return fCurrPtCount; }
2535 const SkPoint* pts() const { return fCurrPt; }
2536 void next();
2537
2538private:
2539 int fCurrPtCount;
2540 const SkPoint* fCurrPt;
2541 const uint8_t* fCurrVerb;
2542 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002543 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002544 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002545 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002546};
2547
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002548ContourIter::ContourIter(const SkPathRef& pathRef) {
2549 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002550 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002551 fCurrPt = pathRef.points();
2552 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002553 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002554 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002555 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002556 this->next();
2557}
2558
2559void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002560 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002561 fDone = true;
2562 }
2563 if (fDone) {
2564 return;
2565 }
2566
2567 // skip pts of prev contour
2568 fCurrPt += fCurrPtCount;
2569
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002570 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002571 int ptCount = 1; // moveTo
2572 const uint8_t* verbs = fCurrVerb;
2573
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002574 for (--verbs; verbs > fStopVerbs; --verbs) {
2575 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002576 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002577 goto CONTOUR_END;
2578 case SkPath::kLine_Verb:
2579 ptCount += 1;
2580 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002581 case SkPath::kConic_Verb:
2582 fCurrConicWeight += 1;
2583 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002584 case SkPath::kQuad_Verb:
2585 ptCount += 2;
2586 break;
2587 case SkPath::kCubic_Verb:
2588 ptCount += 3;
2589 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002590 case SkPath::kClose_Verb:
2591 break;
2592 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002593 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002594 break;
2595 }
2596 }
2597CONTOUR_END:
2598 fCurrPtCount = ptCount;
2599 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002600 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002601}
2602
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002603// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002604static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002605 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2606 // We may get 0 when the above subtracts underflow. We expect this to be
2607 // very rare and lazily promote to double.
2608 if (0 == cross) {
2609 double p0x = SkScalarToDouble(p0.fX);
2610 double p0y = SkScalarToDouble(p0.fY);
2611
2612 double p1x = SkScalarToDouble(p1.fX);
2613 double p1y = SkScalarToDouble(p1.fY);
2614
2615 double p2x = SkScalarToDouble(p2.fX);
2616 double p2y = SkScalarToDouble(p2.fY);
2617
2618 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2619 (p1y - p0y) * (p2x - p0x));
2620
2621 }
2622 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002623}
2624
reed@google.comc1ea60a2012-01-31 15:15:36 +00002625// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002626static int find_max_y(const SkPoint pts[], int count) {
2627 SkASSERT(count > 0);
2628 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002629 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002630 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002631 SkScalar y = pts[i].fY;
2632 if (y > max) {
2633 max = y;
2634 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002635 }
2636 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002637 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002638}
2639
reed@google.comcabaf1d2012-01-11 21:03:05 +00002640static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2641 int i = index;
2642 for (;;) {
2643 i = (i + inc) % n;
2644 if (i == index) { // we wrapped around, so abort
2645 break;
2646 }
2647 if (pts[index] != pts[i]) { // found a different point, success!
2648 break;
2649 }
2650 }
2651 return i;
2652}
2653
reed@google.comc1ea60a2012-01-31 15:15:36 +00002654/**
2655 * Starting at index, and moving forward (incrementing), find the xmin and
2656 * xmax of the contiguous points that have the same Y.
2657 */
2658static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2659 int* maxIndexPtr) {
2660 const SkScalar y = pts[index].fY;
2661 SkScalar min = pts[index].fX;
2662 SkScalar max = min;
2663 int minIndex = index;
2664 int maxIndex = index;
2665 for (int i = index + 1; i < n; ++i) {
2666 if (pts[i].fY != y) {
2667 break;
2668 }
2669 SkScalar x = pts[i].fX;
2670 if (x < min) {
2671 min = x;
2672 minIndex = i;
2673 } else if (x > max) {
2674 max = x;
2675 maxIndex = i;
2676 }
2677 }
2678 *maxIndexPtr = maxIndex;
2679 return minIndex;
2680}
2681
reed026beb52015-06-10 14:23:15 -07002682static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2683 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002684}
2685
reed@google.comac8543f2012-01-30 20:51:25 +00002686/*
2687 * We loop through all contours, and keep the computed cross-product of the
2688 * contour that contained the global y-max. If we just look at the first
2689 * contour, we may find one that is wound the opposite way (correctly) since
2690 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2691 * that is outer most (or at least has the global y-max) before we can consider
2692 * its cross product.
2693 */
reed026beb52015-06-10 14:23:15 -07002694bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002695 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2696 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002697 return true;
2698 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002699
2700 // don't want to pay the cost for computing this if it
2701 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002702 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2703 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002704 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002705 return false;
2706 }
reed@google.com69a99432012-01-10 18:00:10 +00002707
reed026beb52015-06-10 14:23:15 -07002708 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002709
reed@google.comac8543f2012-01-30 20:51:25 +00002710 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002711 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002712 SkScalar ymaxCross = 0;
2713
reed@google.com69a99432012-01-10 18:00:10 +00002714 for (; !iter.done(); iter.next()) {
2715 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002716 if (n < 3) {
2717 continue;
2718 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002719
reed@google.comcabaf1d2012-01-11 21:03:05 +00002720 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002721 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002722 int index = find_max_y(pts, n);
2723 if (pts[index].fY < ymax) {
2724 continue;
2725 }
2726
2727 // If there is more than 1 distinct point at the y-max, we take the
2728 // x-min and x-max of them and just subtract to compute the dir.
2729 if (pts[(index + 1) % n].fY == pts[index].fY) {
2730 int maxIndex;
2731 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2732 if (minIndex == maxIndex) {
2733 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002734 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002735 SkASSERT(pts[minIndex].fY == pts[index].fY);
2736 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2737 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2738 // we just subtract the indices, and let that auto-convert to
2739 // SkScalar, since we just want - or + to signal the direction.
2740 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002741 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002742 TRY_CROSSPROD:
2743 // Find a next and prev index to use for the cross-product test,
2744 // but we try to find pts that form non-zero vectors from pts[index]
2745 //
2746 // Its possible that we can't find two non-degenerate vectors, so
2747 // we have to guard our search (e.g. all the pts could be in the
2748 // same place).
2749
2750 // we pass n - 1 instead of -1 so we don't foul up % operator by
2751 // passing it a negative LH argument.
2752 int prev = find_diff_pt(pts, index, n, n - 1);
2753 if (prev == index) {
2754 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002755 continue;
2756 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002757 int next = find_diff_pt(pts, index, n, 1);
2758 SkASSERT(next != index);
2759 cross = cross_prod(pts[prev], pts[index], pts[next]);
2760 // if we get a zero and the points are horizontal, then we look at the spread in
2761 // x-direction. We really should continue to walk away from the degeneracy until
2762 // there is a divergence.
2763 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2764 // construct the subtract so we get the correct Direction below
2765 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002766 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002767 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002768
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002769 if (cross) {
2770 // record our best guess so far
2771 ymax = pts[index].fY;
2772 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002773 }
2774 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002775 if (ymaxCross) {
2776 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002777 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002778 return true;
2779 } else {
2780 return false;
2781 }
reed@google.comac8543f2012-01-30 20:51:25 +00002782}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783
2784///////////////////////////////////////////////////////////////////////////////
2785
caryclark9aacd902015-12-14 08:38:09 -08002786static bool between(SkScalar a, SkScalar b, SkScalar c) {
2787 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2788 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2789 return (a - b) * (c - b) <= 0;
2790}
2791
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002792static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2793 SkScalar t) {
2794 SkScalar A = c3 + 3*(c1 - c2) - c0;
2795 SkScalar B = 3*(c2 - c1 - c1 + c0);
2796 SkScalar C = 3*(c1 - c0);
2797 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002798 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799}
2800
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002801template <size_t N> static void find_minmax(const SkPoint pts[],
2802 SkScalar* minPtr, SkScalar* maxPtr) {
2803 SkScalar min, max;
2804 min = max = pts[0].fX;
2805 for (size_t i = 1; i < N; ++i) {
2806 min = SkMinScalar(min, pts[i].fX);
2807 max = SkMaxScalar(max, pts[i].fX);
2808 }
2809 *minPtr = min;
2810 *maxPtr = max;
2811}
2812
caryclark9cb5d752015-12-18 04:35:24 -08002813static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2814 if (start.fY == end.fY) {
2815 return between(start.fX, x, end.fX) && x != end.fX;
2816 } else {
2817 return x == start.fX && y == start.fY;
2818 }
2819}
2820
caryclark9aacd902015-12-14 08:38:09 -08002821static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002822 SkScalar y0 = pts[0].fY;
2823 SkScalar y3 = pts[3].fY;
2824
2825 int dir = 1;
2826 if (y0 > y3) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002827 using std::swap;
2828 swap(y0, y3);
caryclark9cb5d752015-12-18 04:35:24 -08002829 dir = -1;
2830 }
2831 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002832 return 0;
2833 }
caryclark9cb5d752015-12-18 04:35:24 -08002834 if (checkOnCurve(x, y, pts[0], pts[3])) {
2835 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002836 return 0;
2837 }
caryclark9cb5d752015-12-18 04:35:24 -08002838 if (y == y3) {
2839 return 0;
2840 }
2841
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002842 // quickreject or quickaccept
2843 SkScalar min, max;
2844 find_minmax<4>(pts, &min, &max);
2845 if (x < min) {
2846 return 0;
2847 }
2848 if (x > max) {
2849 return dir;
2850 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002851
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002853 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002854 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002855 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002856 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002857 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002858 if (SkScalarNearlyEqual(xt, x)) {
2859 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2860 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002861 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002862 }
2863 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 return xt < x ? dir : 0;
2865}
2866
caryclark9aacd902015-12-14 08:38:09 -08002867static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002868 SkPoint dst[10];
2869 int n = SkChopCubicAtYExtrema(pts, dst);
2870 int w = 0;
2871 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002872 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 }
2874 return w;
2875}
2876
caryclark9aacd902015-12-14 08:38:09 -08002877static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2878 SkASSERT(src);
2879 SkASSERT(t >= 0 && t <= 1);
2880 SkScalar src2w = src[2] * w;
2881 SkScalar C = src[0];
2882 SkScalar A = src[4] - 2 * src2w + C;
2883 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002884 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002885}
2886
2887
2888static double conic_eval_denominator(SkScalar w, SkScalar t) {
2889 SkScalar B = 2 * (w - 1);
2890 SkScalar C = 1;
2891 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002892 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002893}
2894
2895static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2896 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002897 SkScalar y0 = pts[0].fY;
2898 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002899
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002900 int dir = 1;
2901 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002902 using std::swap;
2903 swap(y0, y2);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002904 dir = -1;
2905 }
caryclark9aacd902015-12-14 08:38:09 -08002906 if (y < y0 || y > y2) {
2907 return 0;
2908 }
caryclark9cb5d752015-12-18 04:35:24 -08002909 if (checkOnCurve(x, y, pts[0], pts[2])) {
2910 *onCurveCount += 1;
2911 return 0;
2912 }
caryclark9aacd902015-12-14 08:38:09 -08002913 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 return 0;
2915 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002916
caryclark9aacd902015-12-14 08:38:09 -08002917 SkScalar roots[2];
2918 SkScalar A = pts[2].fY;
2919 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2920 SkScalar C = pts[0].fY;
2921 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2922 B -= C; // B = b*w - w * yCept + yCept - a
2923 C -= y;
2924 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2925 SkASSERT(n <= 1);
2926 SkScalar xt;
2927 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002928 // zero roots are returned only when y0 == y
2929 // Need [0] if dir == 1
2930 // and [2] if dir == -1
2931 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002932 } else {
2933 SkScalar t = roots[0];
2934 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2935 }
2936 if (SkScalarNearlyEqual(xt, x)) {
2937 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2938 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002939 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002940 }
2941 }
2942 return xt < x ? dir : 0;
2943}
2944
2945static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2946 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2947 if (y0 == y1) {
2948 return true;
2949 }
2950 if (y0 < y1) {
2951 return y1 <= y2;
2952 } else {
2953 return y1 >= y2;
2954 }
2955}
2956
2957static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2958 int* onCurveCount) {
2959 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002960 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002961 // If the data points are very large, the conic may not be monotonic but may also
2962 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002963 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2964 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2965 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002966 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2967 }
2968 return w;
2969}
2970
2971static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2972 SkScalar y0 = pts[0].fY;
2973 SkScalar y2 = pts[2].fY;
2974
2975 int dir = 1;
2976 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002977 using std::swap;
2978 swap(y0, y2);
caryclark9aacd902015-12-14 08:38:09 -08002979 dir = -1;
2980 }
2981 if (y < y0 || y > y2) {
2982 return 0;
2983 }
caryclark9cb5d752015-12-18 04:35:24 -08002984 if (checkOnCurve(x, y, pts[0], pts[2])) {
2985 *onCurveCount += 1;
2986 return 0;
2987 }
caryclark9aacd902015-12-14 08:38:09 -08002988 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002989 return 0;
2990 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 // bounds check on X (not required. is it faster?)
2992#if 0
2993 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2994 return 0;
2995 }
2996#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002997
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 SkScalar roots[2];
2999 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3000 2 * (pts[1].fY - pts[0].fY),
3001 pts[0].fY - y,
3002 roots);
3003 SkASSERT(n <= 1);
3004 SkScalar xt;
3005 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003006 // zero roots are returned only when y0 == y
3007 // Need [0] if dir == 1
3008 // and [2] if dir == -1
3009 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 } else {
3011 SkScalar t = roots[0];
3012 SkScalar C = pts[0].fX;
3013 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3014 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003015 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 }
caryclark9aacd902015-12-14 08:38:09 -08003017 if (SkScalarNearlyEqual(xt, x)) {
3018 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3019 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003020 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003021 }
3022 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003023 return xt < x ? dir : 0;
3024}
3025
caryclark9aacd902015-12-14 08:38:09 -08003026static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003027 SkPoint dst[5];
3028 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003029
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003030 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3031 n = SkChopQuadAtYExtrema(pts, dst);
3032 pts = dst;
3033 }
caryclark9aacd902015-12-14 08:38:09 -08003034 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003035 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003036 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003037 }
3038 return w;
3039}
3040
caryclark9aacd902015-12-14 08:38:09 -08003041static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003042 SkScalar x0 = pts[0].fX;
3043 SkScalar y0 = pts[0].fY;
3044 SkScalar x1 = pts[1].fX;
3045 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003046
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003047 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003048
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003049 int dir = 1;
3050 if (y0 > y1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04003051 using std::swap;
3052 swap(y0, y1);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003053 dir = -1;
3054 }
caryclark9aacd902015-12-14 08:38:09 -08003055 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003056 return 0;
3057 }
caryclark9cb5d752015-12-18 04:35:24 -08003058 if (checkOnCurve(x, y, pts[0], pts[1])) {
3059 *onCurveCount += 1;
3060 return 0;
3061 }
3062 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003063 return 0;
3064 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003065 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003066
caryclark9aacd902015-12-14 08:38:09 -08003067 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003068 // zero cross means the point is on the line, and since the case where
3069 // y of the query point is at the end point is handled above, we can be
3070 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003071 if (x != x1 || y != pts[1].fY) {
3072 *onCurveCount += 1;
3073 }
caryclark9aacd902015-12-14 08:38:09 -08003074 dir = 0;
3075 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003076 dir = 0;
3077 }
3078 return dir;
3079}
3080
caryclark9aacd902015-12-14 08:38:09 -08003081static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3082 SkTDArray<SkVector>* tangents) {
3083 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3084 && !between(pts[2].fY, y, pts[3].fY)) {
3085 return;
3086 }
3087 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3088 && !between(pts[2].fX, x, pts[3].fX)) {
3089 return;
3090 }
3091 SkPoint dst[10];
3092 int n = SkChopCubicAtYExtrema(pts, dst);
3093 for (int i = 0; i <= n; ++i) {
3094 SkPoint* c = &dst[i * 3];
3095 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003096 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003097 continue;
mbarbella276e6332016-05-31 14:44:01 -07003098 }
caryclark9aacd902015-12-14 08:38:09 -08003099 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3100 if (!SkScalarNearlyEqual(x, xt)) {
3101 continue;
3102 }
3103 SkVector tangent;
3104 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
Mike Reed5edcd312018-08-08 11:23:41 -04003105 tangents->push_back(tangent);
caryclark9aacd902015-12-14 08:38:09 -08003106 }
3107}
3108
3109static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3110 SkTDArray<SkVector>* tangents) {
3111 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3112 return;
3113 }
3114 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3115 return;
3116 }
3117 SkScalar roots[2];
3118 SkScalar A = pts[2].fY;
3119 SkScalar B = pts[1].fY * w - y * w + y;
3120 SkScalar C = pts[0].fY;
3121 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3122 B -= C; // B = b*w - w * yCept + yCept - a
3123 C -= y;
3124 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3125 for (int index = 0; index < n; ++index) {
3126 SkScalar t = roots[index];
3127 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3128 if (!SkScalarNearlyEqual(x, xt)) {
3129 continue;
3130 }
3131 SkConic conic(pts, w);
Mike Reed5edcd312018-08-08 11:23:41 -04003132 tangents->push_back(conic.evalTangentAt(t));
caryclark9aacd902015-12-14 08:38:09 -08003133 }
3134}
3135
3136static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3137 SkTDArray<SkVector>* tangents) {
3138 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3139 return;
3140 }
3141 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3142 return;
3143 }
3144 SkScalar roots[2];
3145 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3146 2 * (pts[1].fY - pts[0].fY),
3147 pts[0].fY - y,
3148 roots);
3149 for (int index = 0; index < n; ++index) {
3150 SkScalar t = roots[index];
3151 SkScalar C = pts[0].fX;
3152 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3153 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003154 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003155 if (!SkScalarNearlyEqual(x, xt)) {
3156 continue;
3157 }
Mike Reed5edcd312018-08-08 11:23:41 -04003158 tangents->push_back(SkEvalQuadTangentAt(pts, t));
caryclark9aacd902015-12-14 08:38:09 -08003159 }
3160}
3161
3162static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3163 SkTDArray<SkVector>* tangents) {
3164 SkScalar y0 = pts[0].fY;
3165 SkScalar y1 = pts[1].fY;
3166 if (!between(y0, y, y1)) {
3167 return;
3168 }
3169 SkScalar x0 = pts[0].fX;
3170 SkScalar x1 = pts[1].fX;
3171 if (!between(x0, x, x1)) {
3172 return;
3173 }
3174 SkScalar dx = x1 - x0;
3175 SkScalar dy = y1 - y0;
3176 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3177 return;
3178 }
3179 SkVector v;
3180 v.set(dx, dy);
Mike Reed5edcd312018-08-08 11:23:41 -04003181 tangents->push_back(v);
caryclark9aacd902015-12-14 08:38:09 -08003182}
3183
reed@google.com4db592c2013-10-30 17:39:43 +00003184static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3185 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3186}
3187
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188bool SkPath::contains(SkScalar x, SkScalar y) const {
3189 bool isInverse = this->isInverseFillType();
3190 if (this->isEmpty()) {
3191 return isInverse;
3192 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003193
reed@google.com4db592c2013-10-30 17:39:43 +00003194 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003195 return isInverse;
3196 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003197
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003198 SkPath::Iter iter(*this, true);
3199 bool done = false;
3200 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003201 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003202 do {
3203 SkPoint pts[4];
3204 switch (iter.next(pts, false)) {
3205 case SkPath::kMove_Verb:
3206 case SkPath::kClose_Verb:
3207 break;
3208 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003209 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003210 break;
3211 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003212 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003213 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003214 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003215 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003216 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003217 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003218 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003219 break;
3220 case SkPath::kDone_Verb:
3221 done = true;
3222 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003223 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003224 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003225 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3226 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3227 if (evenOddFill) {
3228 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003229 }
caryclark9aacd902015-12-14 08:38:09 -08003230 if (w) {
3231 return !isInverse;
3232 }
3233 if (onCurveCount <= 1) {
3234 return SkToBool(onCurveCount) ^ isInverse;
3235 }
3236 if ((onCurveCount & 1) || evenOddFill) {
3237 return SkToBool(onCurveCount & 1) ^ isInverse;
3238 }
halcanary9d524f22016-03-29 09:03:52 -07003239 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003240 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3241 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003242 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003243 SkTDArray<SkVector> tangents;
3244 do {
3245 SkPoint pts[4];
3246 int oldCount = tangents.count();
3247 switch (iter.next(pts, false)) {
3248 case SkPath::kMove_Verb:
3249 case SkPath::kClose_Verb:
3250 break;
3251 case SkPath::kLine_Verb:
3252 tangent_line(pts, x, y, &tangents);
3253 break;
3254 case SkPath::kQuad_Verb:
3255 tangent_quad(pts, x, y, &tangents);
3256 break;
3257 case SkPath::kConic_Verb:
3258 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3259 break;
3260 case SkPath::kCubic_Verb:
3261 tangent_cubic(pts, x, y, &tangents);
3262 break;
3263 case SkPath::kDone_Verb:
3264 done = true;
3265 break;
3266 }
3267 if (tangents.count() > oldCount) {
3268 int last = tangents.count() - 1;
3269 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003270 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003271 tangents.remove(last);
3272 } else {
3273 for (int index = 0; index < last; ++index) {
3274 const SkVector& test = tangents[index];
3275 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003276 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3277 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003278 tangents.remove(last);
3279 tangents.removeShuffle(index);
3280 break;
3281 }
3282 }
3283 }
3284 }
3285 } while (!done);
3286 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003287}
fmalitaaa0df4e2015-12-01 09:13:23 -08003288
3289int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3290 SkScalar w, SkPoint pts[], int pow2) {
3291 const SkConic conic(p0, p1, p2, w);
3292 return conic.chopIntoQuadsPOW2(pts, pow2);
3293}
bsalomonedc743a2016-06-01 09:42:31 -07003294
3295bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3296 unsigned* start) {
3297 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3298 return false;
3299 }
3300 SkPath::RawIter iter(path);
3301 SkPoint verbPts[4];
3302 SkPath::Verb v;
3303 SkPoint rectPts[5];
3304 int rectPtCnt = 0;
3305 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3306 switch (v) {
3307 case SkPath::kMove_Verb:
3308 if (0 != rectPtCnt) {
3309 return false;
3310 }
3311 rectPts[0] = verbPts[0];
3312 ++rectPtCnt;
3313 break;
3314 case SkPath::kLine_Verb:
3315 if (5 == rectPtCnt) {
3316 return false;
3317 }
3318 rectPts[rectPtCnt] = verbPts[1];
3319 ++rectPtCnt;
3320 break;
3321 case SkPath::kClose_Verb:
3322 if (4 == rectPtCnt) {
3323 rectPts[4] = rectPts[0];
3324 rectPtCnt = 5;
3325 }
3326 break;
3327 default:
3328 return false;
3329 }
3330 }
3331 if (rectPtCnt < 5) {
3332 return false;
3333 }
3334 if (rectPts[0] != rectPts[4]) {
3335 return false;
3336 }
bsalomon057ae8a2016-07-24 05:37:26 -07003337 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3338 // and pts 1 and 2 the opposite vertical or horizontal edge).
3339 bool vec03IsVertical;
3340 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3341 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3342 // Make sure it has non-zero width and height
3343 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003344 return false;
3345 }
bsalomon057ae8a2016-07-24 05:37:26 -07003346 vec03IsVertical = true;
3347 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3348 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3349 // Make sure it has non-zero width and height
3350 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3351 return false;
3352 }
3353 vec03IsVertical = false;
3354 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003355 return false;
3356 }
bsalomon057ae8a2016-07-24 05:37:26 -07003357 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3358 // set if it is on the bottom edge.
3359 unsigned sortFlags =
3360 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3361 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3362 switch (sortFlags) {
3363 case 0b00:
3364 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3365 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3366 *start = 0;
3367 break;
3368 case 0b01:
3369 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3370 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3371 *start = 1;
3372 break;
3373 case 0b10:
3374 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3375 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3376 *start = 3;
3377 break;
3378 case 0b11:
3379 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3380 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3381 *start = 2;
3382 break;
bsalomonedc743a2016-06-01 09:42:31 -07003383 }
bsalomonedc743a2016-06-01 09:42:31 -07003384 return true;
3385}
bsalomon21af9ca2016-08-25 12:29:23 -07003386
Brian Salomone4949402018-04-26 15:22:04 -04003387bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3388 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3389 // This gets converted to an oval.
3390 return true;
3391 }
3392 if (useCenter) {
3393 // This is a pie wedge. It's convex if the angle is <= 180.
3394 return SkScalarAbs(sweepAngle) <= 180.f;
3395 }
3396 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3397 // to a secant, i.e. convex.
3398 return SkScalarAbs(sweepAngle) <= 360.f;
3399}
3400
bsalomon21af9ca2016-08-25 12:29:23 -07003401void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3402 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3403 SkASSERT(!oval.isEmpty());
3404 SkASSERT(sweepAngle);
3405
3406 path->reset();
3407 path->setIsVolatile(true);
3408 path->setFillType(SkPath::kWinding_FillType);
3409 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3410 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003411 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003412 return;
3413 }
3414 if (useCenter) {
3415 path->moveTo(oval.centerX(), oval.centerY());
3416 }
Brian Salomone4949402018-04-26 15:22:04 -04003417 auto firstDir =
3418 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3419 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003420 // Arc to mods at 360 and drawArc is not supposed to.
3421 bool forceMoveTo = !useCenter;
3422 while (sweepAngle <= -360.f) {
3423 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3424 startAngle -= 180.f;
3425 path->arcTo(oval, startAngle, -180.f, false);
3426 startAngle -= 180.f;
3427 forceMoveTo = false;
3428 sweepAngle += 360.f;
3429 }
3430 while (sweepAngle >= 360.f) {
3431 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3432 startAngle += 180.f;
3433 path->arcTo(oval, startAngle, 180.f, false);
3434 startAngle += 180.f;
3435 forceMoveTo = false;
3436 sweepAngle -= 360.f;
3437 }
3438 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3439 if (useCenter) {
3440 path->close();
3441 }
Brian Salomone4949402018-04-26 15:22:04 -04003442 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3443 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003444}
Mike Reed0d7dac82017-02-02 17:45:56 -08003445
3446///////////////////////////////////////////////////////////////////////////////////////////////////
3447#include "SkNx.h"
3448
3449static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3450 SkScalar ts[2];
3451 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3452 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3453 SkASSERT(n >= 0 && n <= 2);
3454 for (int i = 0; i < n; ++i) {
3455 extremas[i] = SkEvalQuadAt(src, ts[i]);
3456 }
3457 extremas[n] = src[2];
3458 return n + 1;
3459}
3460
3461static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3462 SkConic conic(src[0], src[1], src[2], w);
3463 SkScalar ts[2];
3464 int n = conic.findXExtrema(ts);
3465 n += conic.findYExtrema(&ts[n]);
3466 SkASSERT(n >= 0 && n <= 2);
3467 for (int i = 0; i < n; ++i) {
3468 extremas[i] = conic.evalAt(ts[i]);
3469 }
3470 extremas[n] = src[2];
3471 return n + 1;
3472}
3473
3474static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3475 SkScalar ts[4];
3476 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3477 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3478 SkASSERT(n >= 0 && n <= 4);
3479 for (int i = 0; i < n; ++i) {
3480 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3481 }
3482 extremas[n] = src[3];
3483 return n + 1;
3484}
3485
Mike Reed8d3196b2017-02-03 11:34:13 -05003486SkRect SkPath::computeTightBounds() const {
3487 if (0 == this->countVerbs()) {
3488 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003489 }
3490
Mike Reed8d3196b2017-02-03 11:34:13 -05003491 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3492 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003493 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003494
Mike Reed0d7dac82017-02-02 17:45:56 -08003495 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3496 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003497 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003498
3499 // initial with the first MoveTo, so we don't have to check inside the switch
3500 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003501 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003502 for (;;) {
3503 int count = 0;
3504 switch (iter.next(pts)) {
3505 case SkPath::kMove_Verb:
3506 extremas[0] = pts[0];
3507 count = 1;
3508 break;
3509 case SkPath::kLine_Verb:
3510 extremas[0] = pts[1];
3511 count = 1;
3512 break;
3513 case SkPath::kQuad_Verb:
3514 count = compute_quad_extremas(pts, extremas);
3515 break;
3516 case SkPath::kConic_Verb:
3517 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3518 break;
3519 case SkPath::kCubic_Verb:
3520 count = compute_cubic_extremas(pts, extremas);
3521 break;
3522 case SkPath::kClose_Verb:
3523 break;
3524 case SkPath::kDone_Verb:
3525 goto DONE;
3526 }
3527 for (int i = 0; i < count; ++i) {
3528 Sk2s tmp = from_point(extremas[i]);
3529 min = Sk2s::Min(min, tmp);
3530 max = Sk2s::Max(max, tmp);
3531 }
3532 }
3533DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003534 SkRect bounds;
3535 min.store((SkPoint*)&bounds.fLeft);
3536 max.store((SkPoint*)&bounds.fRight);
3537 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003538}
Cary Clarkdf429f32017-11-08 11:44:31 -05003539
3540bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3541 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3542}
3543
3544bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3545 const SkPoint& p3, bool exact) {
3546 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3547 SkPointPriv::EqualsWithinTolerance(p2, p3);
3548}
3549
3550bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3551 const SkPoint& p3, const SkPoint& p4, bool exact) {
3552 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3553 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3554 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3555 SkPointPriv::EqualsWithinTolerance(p3, p4);
3556}