blob: 94ed8cc1a68e95197e1eb28b4d04d39dae39a54b [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
Florin Malitad6614742018-09-12 14:30:13 -040028struct SkPath_Storage_Equivalent {
29 void* fPtr;
30 int32_t fIndex;
31 uint32_t fFlags;
32};
33
34static_assert(sizeof(SkPath) == sizeof(SkPath_Storage_Equivalent),
35 "Please keep an eye on SkPath packing.");
36
Mike Reeda99b6ce2017-02-04 11:04:26 -050037static float poly_eval(float A, float B, float C, float t) {
38 return (A * t + B) * t + C;
39}
40
41static float poly_eval(float A, float B, float C, float D, float t) {
42 return ((A * t + B) * t + C) * t + D;
43}
44
reed@android.com8a1c16f2008-12-17 15:59:43 +000045////////////////////////////////////////////////////////////////////////////
46
reed@google.com3563c9e2011-11-14 19:34:57 +000047/**
48 * Path.bounds is defined to be the bounds of all the control points.
49 * If we called bounds.join(r) we would skip r if r was empty, which breaks
50 * our promise. Hence we have a custom joiner that doesn't look at emptiness
51 */
52static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
53 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
54 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
55 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
56 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
57}
58
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000059static bool is_degenerate(const SkPath& path) {
60 SkPath::Iter iter(path, false);
61 SkPoint pts[4];
62 return SkPath::kDone_Verb == iter.next(pts);
63}
64
bsalomon@google.com30c174b2012-11-13 14:36:42 +000065class SkAutoDisableDirectionCheck {
66public:
67 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070068 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000069 }
70
71 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070072 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000073 }
74
75private:
reed026beb52015-06-10 14:23:15 -070076 SkPath* fPath;
77 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000078};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000079#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000080
reed@android.com8a1c16f2008-12-17 15:59:43 +000081/* This guy's constructor/destructor bracket a path editing operation. It is
82 used when we know the bounds of the amount we are going to add to the path
83 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000084
reed@android.com8a1c16f2008-12-17 15:59:43 +000085 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000086 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000087 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000088
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000089 It also notes if the path was originally degenerate, and if so, sets
90 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000091 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000092 */
93class SkAutoPathBoundsUpdate {
94public:
95 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
96 this->init(path);
97 }
98
99 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
100 SkScalar right, SkScalar bottom) {
101 fRect.set(left, top, right, bottom);
102 this->init(path);
103 }
reed@google.comabf15c12011-01-18 20:35:51 +0000104
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +0000106 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
107 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -0500108 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000109 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000110 }
111 }
reed@google.comabf15c12011-01-18 20:35:51 +0000112
reed@android.com8a1c16f2008-12-17 15:59:43 +0000113private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000114 SkPath* fPath;
115 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000116 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000117 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000118 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000119
reed@android.com6b82d1a2009-06-03 02:35:01 +0000120 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000121 // Cannot use fRect for our bounds unless we know it is sorted
122 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000123 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000124 // Mark the path's bounds as dirty if (1) they are, or (2) the path
125 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000126 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000127 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000128 if (fHasValidBounds && !fEmpty) {
129 joinNoEmptyChecks(&fRect, fPath->getBounds());
130 }
131 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 }
133};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000134#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000135
reed@android.com8a1c16f2008-12-17 15:59:43 +0000136////////////////////////////////////////////////////////////////////////////
137
138/*
139 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000140 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
142
143 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000144 1. If we encounter degenerate segments, remove them
145 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
146 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
147 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148*/
149
150////////////////////////////////////////////////////////////////////////////
151
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000152// flag to require a moveTo if we begin with something else, like lineTo etc.
153#define INITIAL_LASTMOVETOINDEX_VALUE ~0
154
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000155SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800156 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700158 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400159 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000160}
161
162void SkPath::resetFields() {
163 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000164 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000165 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000166 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700167 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000168
169 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700170 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000171}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172
bungeman@google.coma5809a32013-06-21 15:13:34 +0000173SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000174 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000175 this->copyFields(that);
176 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177}
178
179SkPath::~SkPath() {
180 SkDEBUGCODE(this->validate();)
181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183SkPath& SkPath::operator=(const SkPath& that) {
184 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 if (this != &that) {
187 fPathRef.reset(SkRef(that.fPathRef.get()));
188 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000189 }
190 SkDEBUGCODE(this->validate();)
191 return *this;
192}
193
bungeman@google.coma5809a32013-06-21 15:13:34 +0000194void SkPath::copyFields(const SkPath& that) {
195 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000196 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700198 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400199 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400200
201 // Non-atomic assignment of atomic values.
202 fConvexity .store(that.fConvexity .load());
203 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204}
205
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000206bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000207 // note: don't need to look at isConvex or bounds, since just comparing the
208 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000209 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000210 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000211}
212
bungeman@google.coma5809a32013-06-21 15:13:34 +0000213void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000214 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700215 fPathRef.swap(that.fPathRef);
Florin Malitad6614742018-09-12 14:30:13 -0400216 std::swap(fLastMoveToIndex, that.fLastMoveToIndex);
217
218 const auto ft = fFillType;
219 fFillType = that.fFillType;
220 that.fFillType = ft;
221
222 const auto iv = fIsVolatile;
223 fIsVolatile = that.fIsVolatile;
224 that.fIsVolatile = iv;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400225
226 // Non-atomic swaps of atomic values.
227 Convexity c = fConvexity.load();
228 fConvexity.store(that.fConvexity.load());
229 that.fConvexity.store(c);
230
231 uint8_t fd = fFirstDirection.load();
232 fFirstDirection.store(that.fFirstDirection.load());
233 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 }
235}
236
caryclark8e7b19d2016-02-18 04:11:48 -0800237bool SkPath::isInterpolatable(const SkPath& compare) const {
238 int count = fPathRef->countVerbs();
239 if (count != compare.fPathRef->countVerbs()) {
240 return false;
241 }
242 if (!count) {
243 return true;
244 }
245 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
246 count)) {
247 return false;
248 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800249 return !fPathRef->countWeights() ||
250 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800251 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
252}
253
254bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500255 int pointCount = fPathRef->countPoints();
256 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800257 return false;
258 }
Cary Clark7487ec82018-03-06 09:30:46 -0500259 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800260 return true;
261 }
caryclark8e7b19d2016-02-18 04:11:48 -0800262 out->reset();
263 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700264 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800265 return true;
266}
267
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000268static inline bool check_edge_against_rect(const SkPoint& p0,
269 const SkPoint& p1,
270 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700271 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000272 const SkPoint* edgeBegin;
273 SkVector v;
reed026beb52015-06-10 14:23:15 -0700274 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000275 v = p1 - p0;
276 edgeBegin = &p0;
277 } else {
278 v = p0 - p1;
279 edgeBegin = &p1;
280 }
281 if (v.fX || v.fY) {
282 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500283 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
284 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
285 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
286 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
288 return false;
289 }
290 }
291 return true;
292}
293
294bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
295 // This only handles non-degenerate convex paths currently.
296 if (kConvex_Convexity != this->getConvexity()) {
297 return false;
298 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000299
reed026beb52015-06-10 14:23:15 -0700300 SkPathPriv::FirstDirection direction;
301 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000302 return false;
303 }
304
305 SkPoint firstPt;
306 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500307 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000308 SkPath::Verb verb;
309 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400310 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000311 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000313
Lee Salzmana19f0242017-01-12 13:06:21 -0500314 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000315 int nextPt = -1;
316 switch (verb) {
317 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000318 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000319 SkDEBUGCODE(++moveCnt);
320 firstPt = prevPt = pts[0];
321 break;
322 case kLine_Verb:
323 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000324 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400325 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000326 break;
327 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000328 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000329 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400330 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000331 nextPt = 2;
332 break;
333 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000334 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400335 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000336 nextPt = 3;
337 break;
338 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000339 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 break;
341 default:
342 SkDEBUGFAIL("unknown verb");
343 }
344 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800345 if (SkPath::kConic_Verb == verb) {
346 SkConic orig;
347 orig.set(pts, iter.conicWeight());
348 SkPoint quadPts[5];
349 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800350 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800351
352 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
353 return false;
354 }
355 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
356 return false;
357 }
358 } else {
359 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
360 return false;
361 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000362 }
363 prevPt = pts[nextPt];
364 }
365 }
366
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400367 if (segmentCount) {
368 return check_edge_against_rect(prevPt, firstPt, rect, direction);
369 }
370 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000371}
372
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000373uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000374 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800375#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400376 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
377 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000378#endif
379 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000380}
djsollen@google.come63793a2012-03-21 15:39:03 +0000381
Mike Reedb6317422018-08-15 10:23:39 -0400382SkPath& SkPath::reset() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000383 SkDEBUGCODE(this->validate();)
384
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000385 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000386 this->resetFields();
Mike Reedb6317422018-08-15 10:23:39 -0400387 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000388}
389
Mike Reedb6317422018-08-15 10:23:39 -0400390SkPath& SkPath::rewind() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000391 SkDEBUGCODE(this->validate();)
392
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000393 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000394 this->resetFields();
Mike Reedb6317422018-08-15 10:23:39 -0400395 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000396}
397
fsb1475b02016-01-20 09:51:07 -0800398bool SkPath::isLastContourClosed() const {
399 int verbCount = fPathRef->countVerbs();
400 if (0 == verbCount) {
401 return false;
402 }
403 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
404}
405
reed@google.com7e6c4d12012-05-10 14:05:43 +0000406bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000407 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000408
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000409 if (2 == verbCount) {
410 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
411 if (kLine_Verb == fPathRef->atVerb(1)) {
412 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000413 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000414 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000415 line[0] = pts[0];
416 line[1] = pts[1];
417 }
418 return true;
419 }
420 }
421 return false;
422}
423
caryclark@google.comf1316942011-07-26 19:54:45 +0000424/*
425 Determines if path is a rect by keeping track of changes in direction
426 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000427
caryclark@google.comf1316942011-07-26 19:54:45 +0000428 The direction is computed such that:
429 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000430 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000431 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000432 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000433
caryclark@google.comf1316942011-07-26 19:54:45 +0000434A rectangle cycles up/right/down/left or up/left/down/right.
435
436The test fails if:
437 The path is closed, and followed by a line.
438 A second move creates a new endpoint.
439 A diagonal line is parsed.
440 There's more than four changes of direction.
441 There's a discontinuity on the line (e.g., a move in the middle)
442 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000443 The path contains a quadratic or cubic.
444 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000445 *The rectangle doesn't complete a cycle.
446 *The final point isn't equal to the first point.
447
448 *These last two conditions we relax if we have a 3-edge path that would
449 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000450
caryclark@google.comf1316942011-07-26 19:54:45 +0000451It's OK if the path has:
452 Several colinear line segments composing a rectangle side.
453 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000454
caryclark@google.comf1316942011-07-26 19:54:45 +0000455The direction takes advantage of the corners found since opposite sides
456must travel in opposite directions.
457
458FIXME: Allow colinear quads and cubics to be treated like lines.
459FIXME: If the API passes fill-only, return true if the filled stroke
460 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000461
Cary Clark48c464a2018-04-16 12:06:07 -0400462 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000463 0x1 is set if the segment is horizontal
464 0x2 is set if the segment is moving to the right or down
465 thus:
466 two directions are opposites iff (dirA ^ dirB) == 0x2
467 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000468
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000470static int rect_make_dir(SkScalar dx, SkScalar dy) {
471 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
472}
Cary Clark88ba9712018-04-12 14:00:24 -0400473
caryclark@google.comf68154a2012-11-21 15:18:06 +0000474bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400475 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400477 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400478 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400479 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
480 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400481 SkPoint firstCorner;
482 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000483 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400484 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400485 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400486 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000487 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000488 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700489 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000490 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000491 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700492 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
493 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000495 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700497 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000498 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400499 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400500 lastPt = pts;
501 }
Cary Clark88ba9712018-04-12 14:00:24 -0400502 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
503 SkVector lineDelta = lineEnd - lineStart;
504 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000505 return false; // diagonal
506 }
Cary Clark1eece782018-04-20 10:14:41 -0400507 if (!lineDelta.isFinite()) {
508 return false; // path contains infinity or NaN
509 }
Cary Clark88ba9712018-04-12 14:00:24 -0400510 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 break; // single point on side OK
512 }
Cary Clark48c464a2018-04-16 12:06:07 -0400513 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400515 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000516 corners = 1;
517 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400518 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 break;
520 }
521 if (closedOrMoved) {
522 return false; // closed followed by a line
523 }
Cary Clark48c464a2018-04-16 12:06:07 -0400524 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000525 break; // colinear with first
526 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000527 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400528 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400529 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400530 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400531 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400532 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000533 break; // colinear segment
534 }
Cary Clark48c464a2018-04-16 12:06:07 -0400535 directions[corners++] = nextDirection;
536 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400537 switch (corners) {
538 case 2:
539 firstCorner = lineStart;
540 break;
541 case 3:
542 if ((directions[0] ^ directions[2]) != 2) {
543 return false;
544 }
545 thirdCorner = lineEnd;
546 break;
547 case 4:
548 if ((directions[1] ^ directions[3]) != 2) {
549 return false;
550 }
551 break;
552 default:
553 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000554 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400555 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000556 break;
557 }
558 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000559 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000560 case kCubic_Verb:
561 return false; // quadratic, cubic not allowed
562 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400563 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700564 insertClose = true;
565 *currVerb -= 1; // try move again afterwards
566 goto addMissingClose;
567 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400568 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400569 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400570 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400571 closeXY = *firstPt - *lastPt;
572 if (closeXY.fX && closeXY.fY) {
573 return false; // we're diagonal, abort
574 }
Cary Clark8540e112018-04-11 14:30:27 -0400575 }
Cary Clark88ba9712018-04-12 14:00:24 -0400576 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000577 closedOrMoved = true;
578 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000579 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000580 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000581 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000582 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000583 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700584addMissingClose:
585 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000586 }
587 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400588 if (corners < 3 || corners > 4) {
589 return false;
590 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000591 if (savePts) {
592 *ptsPtr = savePts;
593 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400594 // check if close generates diagonal
595 closeXY = *firstPt - *lastPt;
596 if (closeXY.fX && closeXY.fY) {
597 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400598 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400599 if (rect) {
600 rect->set(firstCorner, thirdCorner);
601 }
602 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000603 *isClosed = autoClose;
604 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400605 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400606 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000607 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400608 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609}
610
robertphillips4f662e62014-12-29 14:06:51 -0800611bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000612 SkDEBUGCODE(this->validate();)
613 int currVerb = 0;
614 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400615 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000616}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000617
caryclark95bc5f32015-04-08 08:34:15 -0700618bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000619 SkDEBUGCODE(this->validate();)
620 int currVerb = 0;
621 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000622 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400623 SkRect testRects[2];
624 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000625 return false;
626 }
Cary Clark5c715182018-04-09 16:07:11 -0400627 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000628 if (testRects[0].contains(testRects[1])) {
629 if (rects) {
630 rects[0] = testRects[0];
631 rects[1] = testRects[1];
632 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000633 if (dirs) {
634 dirs[0] = testDirs[0];
635 dirs[1] = testDirs[1];
636 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000637 return true;
638 }
639 if (testRects[1].contains(testRects[0])) {
640 if (rects) {
641 rects[0] = testRects[1];
642 rects[1] = testRects[0];
643 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000644 if (dirs) {
645 dirs[0] = testDirs[1];
646 dirs[1] = testDirs[0];
647 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000648 return true;
649 }
650 }
651 return false;
652}
653
Mike Reed0c3137c2018-02-20 13:57:05 -0500654bool SkPath::isOval(SkRect* bounds) const {
655 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
656}
657
658bool SkPath::isRRect(SkRRect* rrect) const {
659 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
660}
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662int SkPath::countPoints() const {
663 return fPathRef->countPoints();
664}
665
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000666int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000667 SkDEBUGCODE(this->validate();)
668
669 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000670 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800672 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000674}
675
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000676SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000677 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
678 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000679 }
680 return SkPoint::Make(0, 0);
681}
682
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683int SkPath::countVerbs() const {
684 return fPathRef->countVerbs();
685}
686
687static inline void copy_verbs_reverse(uint8_t* inorderDst,
688 const uint8_t* reversedSrc,
689 int count) {
690 for (int i = 0; i < count; ++i) {
691 inorderDst[i] = reversedSrc[~i];
692 }
693}
694
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000695int SkPath::getVerbs(uint8_t dst[], int max) const {
696 SkDEBUGCODE(this->validate();)
697
698 SkASSERT(max >= 0);
699 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000700 int count = SkMin32(max, fPathRef->countVerbs());
701 copy_verbs_reverse(dst, fPathRef->verbs(), count);
702 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000703}
704
reed@google.com294dd7b2011-10-11 11:58:32 +0000705bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706 SkDEBUGCODE(this->validate();)
707
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000708 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000709 if (count > 0) {
710 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000711 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000712 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000713 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000715 if (lastPt) {
716 lastPt->set(0, 0);
717 }
718 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719}
720
caryclarkaec25102015-04-29 08:28:30 -0700721void SkPath::setPt(int index, SkScalar x, SkScalar y) {
722 SkDEBUGCODE(this->validate();)
723
724 int count = fPathRef->countPoints();
725 if (count <= index) {
726 return;
727 } else {
728 SkPathRef::Editor ed(&fPathRef);
729 ed.atPoint(index)->set(x, y);
730 }
731}
732
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733void SkPath::setLastPt(SkScalar x, SkScalar y) {
734 SkDEBUGCODE(this->validate();)
735
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000736 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737 if (count == 0) {
738 this->moveTo(x, y);
739 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 SkPathRef::Editor ed(&fPathRef);
741 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742 }
743}
744
reed@google.com04863fa2011-05-15 04:08:24 +0000745void SkPath::setConvexity(Convexity c) {
746 if (fConvexity != c) {
747 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000748 }
749}
750
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751//////////////////////////////////////////////////////////////////////////////
752// Construction methods
753
reed026beb52015-06-10 14:23:15 -0700754#define DIRTY_AFTER_EDIT \
755 do { \
756 fConvexity = kUnknown_Convexity; \
757 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000758 } while (0)
759
reed@android.com8a1c16f2008-12-17 15:59:43 +0000760void SkPath::incReserve(U16CPU inc) {
761 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000762 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763 SkDEBUGCODE(this->validate();)
764}
765
Mike Reedb6317422018-08-15 10:23:39 -0400766SkPath& SkPath::moveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000767 SkDEBUGCODE(this->validate();)
768
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000771 // remember our index
772 fLastMoveToIndex = fPathRef->countPoints();
773
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000774 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700775
776 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400777 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778}
779
Mike Reedb6317422018-08-15 10:23:39 -0400780SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781 SkPoint pt;
782 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400783 return this->moveTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784}
785
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000786void SkPath::injectMoveToIfNeeded() {
787 if (fLastMoveToIndex < 0) {
788 SkScalar x, y;
789 if (fPathRef->countVerbs() == 0) {
790 x = y = 0;
791 } else {
792 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
793 x = pt.fX;
794 y = pt.fY;
795 }
796 this->moveTo(x, y);
797 }
798}
799
Mike Reedb6317422018-08-15 10:23:39 -0400800SkPath& SkPath::lineTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 SkDEBUGCODE(this->validate();)
802
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000803 this->injectMoveToIfNeeded();
804
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000805 SkPathRef::Editor ed(&fPathRef);
806 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807
reed@google.comb54455e2011-05-16 14:16:04 +0000808 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400809 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810}
811
Mike Reedb6317422018-08-15 10:23:39 -0400812SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000813 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814 SkPoint pt;
815 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400816 return this->lineTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000817}
818
Mike Reedb6317422018-08-15 10:23:39 -0400819SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000821
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000822 this->injectMoveToIfNeeded();
823
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000824 SkPathRef::Editor ed(&fPathRef);
825 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 pts[0].set(x1, y1);
827 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000828
reed@google.comb54455e2011-05-16 14:16:04 +0000829 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400830 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000831}
832
Mike Reedb6317422018-08-15 10:23:39 -0400833SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000834 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835 SkPoint pt;
836 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400837 return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838}
839
Mike Reedb6317422018-08-15 10:23:39 -0400840SkPath& SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
841 SkScalar w) {
reed@google.com277c3f82013-05-31 15:17:50 +0000842 // check for <= 0 or NaN with this test
843 if (!(w > 0)) {
844 this->lineTo(x2, y2);
845 } else if (!SkScalarIsFinite(w)) {
846 this->lineTo(x1, y1);
847 this->lineTo(x2, y2);
848 } else if (SK_Scalar1 == w) {
849 this->quadTo(x1, y1, x2, y2);
850 } else {
851 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000852
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000853 this->injectMoveToIfNeeded();
854
reed@google.com277c3f82013-05-31 15:17:50 +0000855 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000856 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000857 pts[0].set(x1, y1);
858 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000859
reed@google.com277c3f82013-05-31 15:17:50 +0000860 DIRTY_AFTER_EDIT;
861 }
Mike Reedb6317422018-08-15 10:23:39 -0400862 return *this;
reed@google.com277c3f82013-05-31 15:17:50 +0000863}
864
Mike Reedb6317422018-08-15 10:23:39 -0400865SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
866 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000867 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000868 SkPoint pt;
869 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400870 return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000871}
872
Mike Reedb6317422018-08-15 10:23:39 -0400873SkPath& SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
874 SkScalar x3, SkScalar y3) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 SkDEBUGCODE(this->validate();)
876
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000877 this->injectMoveToIfNeeded();
878
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 SkPathRef::Editor ed(&fPathRef);
880 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 pts[0].set(x1, y1);
882 pts[1].set(x2, y2);
883 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884
reed@google.comb54455e2011-05-16 14:16:04 +0000885 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400886 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887}
888
Mike Reedb6317422018-08-15 10:23:39 -0400889SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
890 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000891 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892 SkPoint pt;
893 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400894 return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
895 pt.fX + x3, pt.fY + y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896}
897
Mike Reedb6317422018-08-15 10:23:39 -0400898SkPath& SkPath::close() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000899 SkDEBUGCODE(this->validate();)
900
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000901 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000902 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000903 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 case kLine_Verb:
905 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000906 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000907 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000908 case kMove_Verb: {
909 SkPathRef::Editor ed(&fPathRef);
910 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000911 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000912 }
reed@google.com277c3f82013-05-31 15:17:50 +0000913 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000914 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000915 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000916 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000917 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000918 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000919 }
920 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000921
922 // signal that we need a moveTo to follow us (unless we're done)
923#if 0
924 if (fLastMoveToIndex >= 0) {
925 fLastMoveToIndex = ~fLastMoveToIndex;
926 }
927#else
928 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
929#endif
Mike Reedb6317422018-08-15 10:23:39 -0400930 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000931}
932
933///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000934
fmalitac08d53e2015-11-17 09:53:29 -0800935namespace {
936
937template <unsigned N>
938class PointIterator {
939public:
940 PointIterator(SkPath::Direction dir, unsigned startIndex)
941 : fCurrent(startIndex % N)
942 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
943
944 const SkPoint& current() const {
945 SkASSERT(fCurrent < N);
946 return fPts[fCurrent];
947 }
948
949 const SkPoint& next() {
950 fCurrent = (fCurrent + fAdvance) % N;
951 return this->current();
952 }
953
954protected:
955 SkPoint fPts[N];
956
957private:
958 unsigned fCurrent;
959 unsigned fAdvance;
960};
961
962class RectPointIterator : public PointIterator<4> {
963public:
964 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
965 : PointIterator(dir, startIndex) {
966
967 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
968 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
969 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
970 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
971 }
972};
973
974class OvalPointIterator : public PointIterator<4> {
975public:
976 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
977 : PointIterator(dir, startIndex) {
978
979 const SkScalar cx = oval.centerX();
980 const SkScalar cy = oval.centerY();
981
982 fPts[0] = SkPoint::Make(cx, oval.fTop);
983 fPts[1] = SkPoint::Make(oval.fRight, cy);
984 fPts[2] = SkPoint::Make(cx, oval.fBottom);
985 fPts[3] = SkPoint::Make(oval.fLeft, cy);
986 }
987};
988
989class RRectPointIterator : public PointIterator<8> {
990public:
991 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
992 : PointIterator(dir, startIndex) {
993
994 const SkRect& bounds = rrect.getBounds();
995 const SkScalar L = bounds.fLeft;
996 const SkScalar T = bounds.fTop;
997 const SkScalar R = bounds.fRight;
998 const SkScalar B = bounds.fBottom;
999
1000 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
1001 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
1002 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
1003 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
1004 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
1005 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
1006 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
1007 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
1008 }
1009};
1010
1011} // anonymous namespace
1012
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001013static void assert_known_direction(int dir) {
1014 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
1015}
1016
Mike Reedb6317422018-08-15 10:23:39 -04001017SkPath& SkPath::addRect(const SkRect& rect, Direction dir) {
1018 return this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019}
1020
Mike Reedb6317422018-08-15 10:23:39 -04001021SkPath& SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022 SkScalar bottom, Direction dir) {
Mike Reedb6317422018-08-15 10:23:39 -04001023 return this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
fmalitac08d53e2015-11-17 09:53:29 -08001024}
1025
Mike Reedb6317422018-08-15 10:23:39 -04001026SkPath& SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001027 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001028 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001029 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001030 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001031 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001032
fmalitac08d53e2015-11-17 09:53:29 -08001033 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034
fmalitac08d53e2015-11-17 09:53:29 -08001035 const int kVerbs = 5; // moveTo + 3x lineTo + close
1036 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037
fmalitac08d53e2015-11-17 09:53:29 -08001038 RectPointIterator iter(rect, dir, startIndex);
1039
1040 this->moveTo(iter.current());
1041 this->lineTo(iter.next());
1042 this->lineTo(iter.next());
1043 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001044 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001045
1046 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
Mike Reedb6317422018-08-15 10:23:39 -04001047 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001048}
1049
Mike Reedb6317422018-08-15 10:23:39 -04001050SkPath& SkPath::addPoly(const SkPoint pts[], int count, bool close) {
reed@google.com744faba2012-05-29 19:54:52 +00001051 SkDEBUGCODE(this->validate();)
1052 if (count <= 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001053 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001054 }
1055
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001056 fLastMoveToIndex = fPathRef->countPoints();
1057
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001058 // +close makes room for the extra kClose_Verb
1059 SkPathRef::Editor ed(&fPathRef, count+close, count);
1060
1061 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001062 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001063 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1064 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001065 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001066
reed@google.com744faba2012-05-29 19:54:52 +00001067 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001068 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001069 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001070 }
1071
reed@google.com744faba2012-05-29 19:54:52 +00001072 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001073 SkDEBUGCODE(this->validate();)
Mike Reedb6317422018-08-15 10:23:39 -04001074 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001075}
1076
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001077#include "SkGeometry.h"
1078
reedf90ea012015-01-29 12:03:58 -08001079static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1080 SkPoint* pt) {
1081 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001082 // Chrome uses this path to move into and out of ovals. If not
1083 // treated as a special case the moves can distort the oval's
1084 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001085 pt->set(oval.fRight, oval.centerY());
1086 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001087 } else if (0 == oval.width() && 0 == oval.height()) {
1088 // Chrome will sometimes create 0 radius round rects. Having degenerate
1089 // quad segments in the path prevents the path from being recognized as
1090 // a rect.
1091 // TODO: optimizing the case where only one of width or height is zero
1092 // should also be considered. This case, however, doesn't seem to be
1093 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001094 pt->set(oval.fRight, oval.fTop);
1095 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001096 }
reedf90ea012015-01-29 12:03:58 -08001097 return false;
1098}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099
reedd5d27d92015-02-09 13:54:43 -08001100// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1101//
1102static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1103 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1104 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1105 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001106
1107 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001108 loss in radians-conversion and/or sin/cos, we may end up with coincident
1109 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1110 of drawing a nearly complete circle (good).
1111 e.g. canvas.drawArc(0, 359.99, ...)
1112 -vs- canvas.drawArc(0, 359.9, ...)
1113 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001114 */
reedd5d27d92015-02-09 13:54:43 -08001115 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001116 SkScalar sw = SkScalarAbs(sweepAngle);
1117 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1118 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1119 // make a guess at a tiny angle (in radians) to tweak by
1120 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1121 // not sure how much will be enough, so we use a loop
1122 do {
1123 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001124 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1125 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001126 }
1127 }
reedd5d27d92015-02-09 13:54:43 -08001128 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1129}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001130
reed9e779d42015-02-17 11:43:14 -08001131/**
1132 * If this returns 0, then the caller should just line-to the singlePt, else it should
1133 * ignore singlePt and append the specified number of conics.
1134 */
reedd5d27d92015-02-09 13:54:43 -08001135static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001136 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1137 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001138 SkMatrix matrix;
1139
1140 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1141 matrix.postTranslate(oval.centerX(), oval.centerY());
1142
reed9e779d42015-02-17 11:43:14 -08001143 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1144 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001145 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001146 }
1147 return count;
reedd5d27d92015-02-09 13:54:43 -08001148}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001149
Mike Reedb6317422018-08-15 10:23:39 -04001150SkPath& SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001152 SkRRect rrect;
1153 rrect.setRectRadii(rect, (const SkVector*) radii);
Mike Reedb6317422018-08-15 10:23:39 -04001154 return this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001155}
1156
Mike Reedb6317422018-08-15 10:23:39 -04001157SkPath& SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001158 // legacy start indices: 6 (CW) and 7(CCW)
Mike Reedb6317422018-08-15 10:23:39 -04001159 return this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
fmalitac08d53e2015-11-17 09:53:29 -08001160}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001161
Mike Reedb6317422018-08-15 10:23:39 -04001162SkPath& SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1163 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001164
Mike Reedb6317422018-08-15 10:23:39 -04001165 bool isRRect = hasOnlyMoveTos();
1166 const SkRect& bounds = rrect.getBounds();
fmalitac08d53e2015-11-17 09:53:29 -08001167
Mike Reedb6317422018-08-15 10:23:39 -04001168 if (rrect.isRect() || rrect.isEmpty()) {
1169 // degenerate(rect) => radii points are collapsing
1170 this->addRect(bounds, dir, (startIndex + 1) / 2);
1171 } else if (rrect.isOval()) {
1172 // degenerate(oval) => line points are collapsing
1173 this->addOval(bounds, dir, startIndex / 2);
1174 } else {
1175 fFirstDirection = this->hasOnlyMoveTos() ?
1176 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
fmalitac08d53e2015-11-17 09:53:29 -08001177
Mike Reedb6317422018-08-15 10:23:39 -04001178 SkAutoPathBoundsUpdate apbu(this, bounds);
1179 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001180
Mike Reedb6317422018-08-15 10:23:39 -04001181 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1182 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1183 const SkScalar weight = SK_ScalarRoot2Over2;
fmalitac08d53e2015-11-17 09:53:29 -08001184
Mike Reedb6317422018-08-15 10:23:39 -04001185 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1186 const int kVerbs = startsWithConic
1187 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1188 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1189 this->incReserve(kVerbs);
fmalitac08d53e2015-11-17 09:53:29 -08001190
Mike Reedb6317422018-08-15 10:23:39 -04001191 RRectPointIterator rrectIter(rrect, dir, startIndex);
1192 // Corner iterator indices follow the collapsed radii model,
1193 // adjusted such that the start pt is "behind" the radii start pt.
1194 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1195 RectPointIterator rectIter(bounds, dir, rectStartIndex);
fmalitac08d53e2015-11-17 09:53:29 -08001196
Mike Reedb6317422018-08-15 10:23:39 -04001197 this->moveTo(rrectIter.current());
1198 if (startsWithConic) {
1199 for (unsigned i = 0; i < 3; ++i) {
fmalitac08d53e2015-11-17 09:53:29 -08001200 this->conicTo(rectIter.next(), rrectIter.next(), weight);
Mike Reedb6317422018-08-15 10:23:39 -04001201 this->lineTo(rrectIter.next());
fmalitac08d53e2015-11-17 09:53:29 -08001202 }
Mike Reedb6317422018-08-15 10:23:39 -04001203 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1204 // final lineTo handled by close().
1205 } else {
1206 for (unsigned i = 0; i < 4; ++i) {
1207 this->lineTo(rrectIter.next());
1208 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1209 }
fmalitac08d53e2015-11-17 09:53:29 -08001210 }
Mike Reedb6317422018-08-15 10:23:39 -04001211 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001212
Mike Reedb6317422018-08-15 10:23:39 -04001213 SkPathRef::Editor ed(&fPathRef);
1214 ed.setIsRRect(isRRect, dir, startIndex % 8);
1215
1216 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1217 }
1218
1219 SkDEBUGCODE(fPathRef->validate();)
1220 return *this;
reed@google.com4ed0fb72012-12-12 20:48:18 +00001221}
1222
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001223bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001224 int count = fPathRef->countVerbs();
1225 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1226 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001227 if (*verbs == kLine_Verb ||
1228 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001229 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001230 *verbs == kCubic_Verb) {
1231 return false;
1232 }
1233 ++verbs;
1234 }
1235 return true;
1236}
1237
Brian Osmana2318572017-07-10 15:09:26 -04001238bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1239 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001240 if (count < 2) {
1241 return true;
1242 }
Brian Osmana2318572017-07-10 15:09:26 -04001243 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001244 const SkPoint& first = *pts;
1245 for (int index = 1; index < count; ++index) {
1246 if (first != pts[index]) {
1247 return false;
1248 }
1249 }
1250 return true;
1251}
1252
Mike Reedb6317422018-08-15 10:23:39 -04001253SkPath& SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1254 Direction dir) {
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001255 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001256
humper@google.com75e3ca12013-04-08 21:44:11 +00001257 if (rx < 0 || ry < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001258 return *this;
humper@google.com75e3ca12013-04-08 21:44:11 +00001259 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001260
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001261 SkRRect rrect;
1262 rrect.setRectXY(rect, rx, ry);
Mike Reedb6317422018-08-15 10:23:39 -04001263 return this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001264}
1265
Mike Reedb6317422018-08-15 10:23:39 -04001266SkPath& SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001267 // legacy start index: 1
Mike Reedb6317422018-08-15 10:23:39 -04001268 return this->addOval(oval, dir, 1);
fmalitac08d53e2015-11-17 09:53:29 -08001269}
1270
Mike Reedb6317422018-08-15 10:23:39 -04001271SkPath& SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001272 assert_known_direction(dir);
1273
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001274 /* If addOval() is called after previous moveTo(),
1275 this path is still marked as an oval. This is used to
1276 fit into WebKit's calling sequences.
1277 We can't simply check isEmpty() in this case, as additional
1278 moveTo() would mark the path non empty.
1279 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001280 bool isOval = hasOnlyMoveTos();
1281 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001282 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001283 } else {
reed026beb52015-06-10 14:23:15 -07001284 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001285 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001286
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001287 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001288 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289
fmalitac08d53e2015-11-17 09:53:29 -08001290 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1291 const int kVerbs = 6; // moveTo + 4x conicTo + close
1292 this->incReserve(kVerbs);
1293
1294 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1295 // The corner iterator pts are tracking "behind" the oval/radii pts.
1296 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001297 const SkScalar weight = SK_ScalarRoot2Over2;
1298
fmalitac08d53e2015-11-17 09:53:29 -08001299 this->moveTo(ovalIter.current());
1300 for (unsigned i = 0; i < 4; ++i) {
1301 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001302 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001303 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001304
fmalitac08d53e2015-11-17 09:53:29 -08001305 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1306
robertphillips@google.com466310d2013-12-03 16:43:54 +00001307 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001308
bsalomon78d58d12016-05-27 09:17:04 -07001309 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
Mike Reedb6317422018-08-15 10:23:39 -04001310 return *this;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001311}
1312
Mike Reedb6317422018-08-15 10:23:39 -04001313SkPath& SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001315 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316 }
Mike Reedb6317422018-08-15 10:23:39 -04001317 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318}
1319
Mike Reedb6317422018-08-15 10:23:39 -04001320SkPath& SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1321 bool forceMoveTo) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001322 if (oval.width() < 0 || oval.height() < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001323 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 }
1325
reedf90ea012015-01-29 12:03:58 -08001326 if (fPathRef->countVerbs() == 0) {
1327 forceMoveTo = true;
1328 }
1329
1330 SkPoint lonePt;
1331 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
Mike Reedb6317422018-08-15 10:23:39 -04001332 return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
reedf90ea012015-01-29 12:03:58 -08001333 }
1334
reedd5d27d92015-02-09 13:54:43 -08001335 SkVector startV, stopV;
1336 SkRotationDirection dir;
1337 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1338
reed9e779d42015-02-17 11:43:14 -08001339 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001340
Brian Salomone4949402018-04-26 15:22:04 -04001341 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1342 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1343 // arcs from the same oval.
1344 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1345 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001346 if (forceMoveTo) {
1347 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001348 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001349 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1350 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1351 this->lineTo(pt);
1352 }
1353 };
1354
xidachen6069dda2016-10-06 05:42:23 -07001355 // At this point, we know that the arc is not a lone point, but startV == stopV
1356 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1357 // cannot handle it.
1358 if (startV == stopV) {
1359 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1360 SkScalar radiusX = oval.width() / 2;
1361 SkScalar radiusY = oval.height() / 2;
1362 // We cannot use SkScalarSinCos function in the next line because
1363 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1364 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001365 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001366 // make sin(endAngle) to be 0 which will then draw a dot.
1367 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1368 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001369 addPt(singlePt);
Mike Reedb6317422018-08-15 10:23:39 -04001370 return *this;
xidachen6069dda2016-10-06 05:42:23 -07001371 }
1372
reedd5d27d92015-02-09 13:54:43 -08001373 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001374 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001375 if (count) {
1376 this->incReserve(count * 2 + 1);
1377 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001378 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001379 for (int i = 0; i < count; ++i) {
1380 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1381 }
reed9e779d42015-02-17 11:43:14 -08001382 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001383 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001384 }
Mike Reedb6317422018-08-15 10:23:39 -04001385 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386}
1387
caryclark55d49052016-01-23 05:07:04 -08001388// This converts the SVG arc to conics.
1389// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1390// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1391// See also SVG implementation notes:
1392// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1393// Note that arcSweep bool value is flipped from the original implementation.
Mike Reedb6317422018-08-15 10:23:39 -04001394SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1395 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001396 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001397 SkPoint srcPts[2];
1398 this->getLastPt(&srcPts[0]);
1399 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1400 // joining the endpoints.
1401 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1402 if (!rx || !ry) {
Mike Reedb6317422018-08-15 10:23:39 -04001403 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001404 }
1405 // If the current point and target point for the arc are identical, it should be treated as a
1406 // zero length path. This ensures continuity in animations.
1407 srcPts[1].set(x, y);
1408 if (srcPts[0] == srcPts[1]) {
Mike Reedb6317422018-08-15 10:23:39 -04001409 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001410 }
1411 rx = SkScalarAbs(rx);
1412 ry = SkScalarAbs(ry);
1413 SkVector midPointDistance = srcPts[0] - srcPts[1];
1414 midPointDistance *= 0.5f;
1415
1416 SkMatrix pointTransform;
1417 pointTransform.setRotate(-angle);
1418
1419 SkPoint transformedMidPoint;
1420 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1421 SkScalar squareRx = rx * rx;
1422 SkScalar squareRy = ry * ry;
1423 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1424 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1425
1426 // Check if the radii are big enough to draw the arc, scale radii if not.
1427 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1428 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1429 if (radiiScale > 1) {
1430 radiiScale = SkScalarSqrt(radiiScale);
1431 rx *= radiiScale;
1432 ry *= radiiScale;
1433 }
1434
1435 pointTransform.setScale(1 / rx, 1 / ry);
1436 pointTransform.preRotate(-angle);
1437
1438 SkPoint unitPts[2];
1439 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1440 SkVector delta = unitPts[1] - unitPts[0];
1441
1442 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1443 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1444
1445 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1446 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1447 scaleFactor = -scaleFactor;
1448 }
1449 delta.scale(scaleFactor);
1450 SkPoint centerPoint = unitPts[0] + unitPts[1];
1451 centerPoint *= 0.5f;
1452 centerPoint.offset(-delta.fY, delta.fX);
1453 unitPts[0] -= centerPoint;
1454 unitPts[1] -= centerPoint;
1455 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1456 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1457 SkScalar thetaArc = theta2 - theta1;
1458 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1459 thetaArc += SK_ScalarPI * 2;
1460 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1461 thetaArc -= SK_ScalarPI * 2;
1462 }
1463 pointTransform.setRotate(angle);
1464 pointTransform.preScale(rx, ry);
1465
Cary Clark1acd3c72017-12-08 11:37:01 -05001466 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1467 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
caryclark55d49052016-01-23 05:07:04 -08001468 SkScalar thetaWidth = thetaArc / segments;
1469 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1470 if (!SkScalarIsFinite(t)) {
Mike Reedb6317422018-08-15 10:23:39 -04001471 return *this;
caryclark55d49052016-01-23 05:07:04 -08001472 }
1473 SkScalar startTheta = theta1;
1474 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001475 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1476 return scalar == SkScalarFloorToScalar(scalar);
1477 };
1478 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1479 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1480 scalar_is_integer(x) && scalar_is_integer(y);
Mike Reed16c92162018-10-01 20:41:38 -04001481
caryclark55d49052016-01-23 05:07:04 -08001482 for (int i = 0; i < segments; ++i) {
1483 SkScalar endTheta = startTheta + thetaWidth;
1484 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1485
1486 unitPts[1].set(cosEndTheta, sinEndTheta);
1487 unitPts[1] += centerPoint;
1488 unitPts[0] = unitPts[1];
1489 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1490 SkPoint mapped[2];
1491 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001492 /*
1493 Computing the arc width introduces rounding errors that cause arcs to start
1494 outside their marks. A round rect may lose convexity as a result. If the input
1495 values are on integers, place the conic on integers as well.
1496 */
Cary Clark1acd3c72017-12-08 11:37:01 -05001497 if (expectIntegers) {
1498 SkScalar* mappedScalars = &mapped[0].fX;
1499 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1500 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1501 }
1502 }
caryclark55d49052016-01-23 05:07:04 -08001503 this->conicTo(mapped[0], mapped[1], w);
1504 startTheta = endTheta;
1505 }
Mike Reedb6317422018-08-15 10:23:39 -04001506 return *this;
caryclark55d49052016-01-23 05:07:04 -08001507}
1508
Mike Reedb6317422018-08-15 10:23:39 -04001509SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1510 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
caryclark55d49052016-01-23 05:07:04 -08001511 SkPoint currentPoint;
1512 this->getLastPt(&currentPoint);
Mike Reedb6317422018-08-15 10:23:39 -04001513 return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
1514 currentPoint.fX + dx, currentPoint.fY + dy);
caryclark55d49052016-01-23 05:07:04 -08001515}
1516
Mike Reedb6317422018-08-15 10:23:39 -04001517SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 if (oval.isEmpty() || 0 == sweepAngle) {
Mike Reedb6317422018-08-15 10:23:39 -04001519 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 }
1521
1522 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1523
1524 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001525 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1526 // See SkPath::addOval() docs.
1527 SkScalar startOver90 = startAngle / 90.f;
1528 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1529 SkScalar error = startOver90 - startOver90I;
1530 if (SkScalarNearlyEqual(error, 0)) {
1531 // Index 1 is at startAngle == 0.
1532 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1533 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
Mike Reedb6317422018-08-15 10:23:39 -04001534 return this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1535 (unsigned) startIndex);
bsalomon1978ce22016-05-31 10:42:16 -07001536 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 }
Mike Reedb6317422018-08-15 10:23:39 -04001538 return this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539}
1540
1541/*
1542 Need to handle the case when the angle is sharp, and our computed end-points
1543 for the arc go behind pt1 and/or p2...
1544*/
Mike Reedb6317422018-08-15 10:23:39 -04001545SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001546 if (radius == 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001547 return this->lineTo(x1, y1);
reeda8b326c2014-12-09 11:50:32 -08001548 }
1549
1550 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001551
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552 // need to know our prev pt so we can construct tangent vectors
1553 {
1554 SkPoint start;
1555 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001556 // Handle degenerate cases by adding a line to the first point and
1557 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 before.setNormalize(x1 - start.fX, y1 - start.fY);
1559 after.setNormalize(x2 - x1, y2 - y1);
1560 }
reed@google.comabf15c12011-01-18 20:35:51 +00001561
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 SkScalar cosh = SkPoint::DotProduct(before, after);
1563 SkScalar sinh = SkPoint::CrossProduct(before, after);
1564
1565 if (SkScalarNearlyZero(sinh)) { // angle is too tight
Mike Reedb6317422018-08-15 10:23:39 -04001566 return this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 }
reed@google.comabf15c12011-01-18 20:35:51 +00001568
Mike Reeda99b6ce2017-02-04 11:04:26 -05001569 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570
Mike Reeda99b6ce2017-02-04 11:04:26 -05001571 SkScalar xx = x1 - dist * before.fX;
1572 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001573 after.setLength(dist);
1574 this->lineTo(xx, yy);
1575 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
Mike Reedb6317422018-08-15 10:23:39 -04001576 return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577}
1578
1579///////////////////////////////////////////////////////////////////////////////
1580
Mike Reedb6317422018-08-15 10:23:39 -04001581SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001582 SkMatrix matrix;
1583
1584 matrix.setTranslate(dx, dy);
Mike Reedb6317422018-08-15 10:23:39 -04001585 return this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586}
1587
Mike Reedc3d8a482018-09-12 10:08:40 -04001588SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) {
1589 // Detect if we're trying to add ourself
1590 const SkPath* src = &srcPath;
1591 SkTLazy<SkPath> tmp;
1592 if (this == src) {
1593 src = tmp.set(srcPath);
1594 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595
Mike Reedc3d8a482018-09-12 10:08:40 -04001596 SkPathRef::Editor(&fPathRef, src->countVerbs(), src->countPoints());
1597
1598 RawIter iter(*src);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001599 SkPoint pts[4];
1600 Verb verb;
1601
Cary Clark9480d822017-10-19 18:01:13 -04001602 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001603 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 while ((verb = iter.next(pts)) != kDone_Verb) {
1605 switch (verb) {
1606 case kMove_Verb:
1607 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001608 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1609 injectMoveToIfNeeded(); // In case last contour is closed
Cary Clark54ff3022018-08-17 10:58:23 -04001610 SkPoint lastPt;
1611 // don't add lineTo if it is degenerate
1612 if (fLastMoveToIndex < 0 || !this->getLastPt(&lastPt) || lastPt != pts[0]) {
1613 this->lineTo(pts[0]);
1614 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001615 } else {
1616 this->moveTo(pts[0]);
1617 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618 break;
1619 case kLine_Verb:
1620 proc(matrix, &pts[1], &pts[1], 1);
1621 this->lineTo(pts[1]);
1622 break;
1623 case kQuad_Verb:
1624 proc(matrix, &pts[1], &pts[1], 2);
1625 this->quadTo(pts[1], pts[2]);
1626 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001627 case kConic_Verb:
1628 proc(matrix, &pts[1], &pts[1], 2);
1629 this->conicTo(pts[1], pts[2], iter.conicWeight());
1630 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 case kCubic_Verb:
1632 proc(matrix, &pts[1], &pts[1], 3);
1633 this->cubicTo(pts[1], pts[2], pts[3]);
1634 break;
1635 case kClose_Verb:
1636 this->close();
1637 break;
1638 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001639 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001641 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642 }
Mike Reedb6317422018-08-15 10:23:39 -04001643 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644}
1645
1646///////////////////////////////////////////////////////////////////////////////
1647
reed@google.com277c3f82013-05-31 15:17:50 +00001648static int pts_in_verb(unsigned verb) {
1649 static const uint8_t gPtsInVerb[] = {
1650 1, // kMove
1651 1, // kLine
1652 2, // kQuad
1653 2, // kConic
1654 3, // kCubic
1655 0, // kClose
1656 0 // kDone
1657 };
1658
1659 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1660 return gPtsInVerb[verb];
1661}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663// ignore the last point of the 1st contour
Mike Reedb6317422018-08-15 10:23:39 -04001664SkPath& SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001665 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1666 if (!verbs) { // empty path returns nullptr
Mike Reedb6317422018-08-15 10:23:39 -04001667 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 }
caryclark51c56782016-11-07 05:09:28 -08001669 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1670 SkASSERT(verbsEnd[0] == kMove_Verb);
1671 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1672 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673
caryclark51c56782016-11-07 05:09:28 -08001674 while (verbs < verbsEnd) {
1675 uint8_t v = *verbs++;
1676 pts -= pts_in_verb(v);
1677 switch (v) {
1678 case kMove_Verb:
1679 // if the path has multiple contours, stop after reversing the last
Mike Reedb6317422018-08-15 10:23:39 -04001680 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001682 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 break;
1684 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001685 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001687 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001688 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001689 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001691 this->cubicTo(pts[2], pts[1], pts[0]);
1692 break;
1693 case kClose_Verb:
1694 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 break;
1696 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001697 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 break;
1699 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 }
Mike Reedb6317422018-08-15 10:23:39 -04001701 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702}
1703
Robert Phillips8051d382018-09-13 08:22:15 -04001704SkPath& SkPath::reverseAddPath(const SkPath& srcPath) {
1705 // Detect if we're trying to add ourself
1706 const SkPath* src = &srcPath;
1707 SkTLazy<SkPath> tmp;
1708 if (this == src) {
1709 src = tmp.set(srcPath);
1710 }
reed@google.com63d73742012-01-10 15:33:12 +00001711
Robert Phillipsf026d892018-09-14 11:07:00 -04001712 SkPathRef::Editor ed(&fPathRef, src->countVerbs(), src->countPoints());
Robert Phillips8051d382018-09-13 08:22:15 -04001713
1714 const SkPoint* pts = src->fPathRef->pointsEnd();
Robert Phillipsf026d892018-09-14 11:07:00 -04001715 // we will iterate through src's verbs backwards
Robert Phillips8051d382018-09-13 08:22:15 -04001716 const uint8_t* verbs = src->fPathRef->verbsMemBegin(); // points at the last verb
1717 const uint8_t* verbsEnd = src->fPathRef->verbs(); // points just past the first verb
1718 const SkScalar* conicWeights = src->fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001719
1720 bool needMove = true;
1721 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 while (verbs < verbsEnd) {
1723 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001724 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001725
1726 if (needMove) {
1727 --pts;
1728 this->moveTo(pts->fX, pts->fY);
1729 needMove = false;
1730 }
1731 pts -= n;
1732 switch (v) {
1733 case kMove_Verb:
1734 if (needClose) {
1735 this->close();
1736 needClose = false;
1737 }
1738 needMove = true;
1739 pts += 1; // so we see the point in "if (needMove)" above
1740 break;
1741 case kLine_Verb:
1742 this->lineTo(pts[0]);
1743 break;
1744 case kQuad_Verb:
1745 this->quadTo(pts[1], pts[0]);
1746 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001747 case kConic_Verb:
1748 this->conicTo(pts[1], pts[0], *--conicWeights);
1749 break;
reed@google.com63d73742012-01-10 15:33:12 +00001750 case kCubic_Verb:
1751 this->cubicTo(pts[2], pts[1], pts[0]);
1752 break;
1753 case kClose_Verb:
1754 needClose = true;
1755 break;
1756 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001757 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001758 }
1759 }
Mike Reedb6317422018-08-15 10:23:39 -04001760 return *this;
reed@google.com63d73742012-01-10 15:33:12 +00001761}
1762
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763///////////////////////////////////////////////////////////////////////////////
1764
1765void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1766 SkMatrix matrix;
1767
1768 matrix.setTranslate(dx, dy);
1769 this->transform(matrix, dst);
1770}
1771
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1773 int level = 2) {
1774 if (--level >= 0) {
1775 SkPoint tmp[7];
1776
1777 SkChopCubicAtHalf(pts, tmp);
1778 subdivide_cubic_to(path, &tmp[0], level);
1779 subdivide_cubic_to(path, &tmp[3], level);
1780 } else {
1781 path->cubicTo(pts[1], pts[2], pts[3]);
1782 }
1783}
1784
1785void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1786 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001787 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 dst = (SkPath*)this;
1789 }
1790
tomhudson@google.com8d430182011-06-06 19:11:19 +00001791 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 SkPath tmp;
1793 tmp.fFillType = fFillType;
1794
1795 SkPath::Iter iter(*this, false);
1796 SkPoint pts[4];
1797 SkPath::Verb verb;
1798
reed@google.com4a3b7142012-05-16 17:16:46 +00001799 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 switch (verb) {
1801 case kMove_Verb:
1802 tmp.moveTo(pts[0]);
1803 break;
1804 case kLine_Verb:
1805 tmp.lineTo(pts[1]);
1806 break;
1807 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001808 // promote the quad to a conic
1809 tmp.conicTo(pts[1], pts[2],
1810 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001812 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001813 tmp.conicTo(pts[1], pts[2],
1814 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001815 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 case kCubic_Verb:
1817 subdivide_cubic_to(&tmp, pts);
1818 break;
1819 case kClose_Verb:
1820 tmp.close();
1821 break;
1822 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001823 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 break;
1825 }
1826 }
1827
1828 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001829 SkPathRef::Editor ed(&dst->fPathRef);
1830 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001831 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001833 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1834
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 if (this != dst) {
Robert Phillipsf026d892018-09-14 11:07:00 -04001836 dst->fLastMoveToIndex = fLastMoveToIndex;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001838 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001839 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001841
reed026beb52015-06-10 14:23:15 -07001842 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1843 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001844 } else {
1845 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001846 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1847 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001848 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001849 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1850 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001851 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001852 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001853 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001854 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001855 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001856 }
1857 }
1858
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859 SkDEBUGCODE(dst->validate();)
1860 }
1861}
1862
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863///////////////////////////////////////////////////////////////////////////////
1864///////////////////////////////////////////////////////////////////////////////
1865
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866SkPath::Iter::Iter() {
1867#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001868 fPts = nullptr;
1869 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001872 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001873#endif
1874 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001875 fVerbs = nullptr;
1876 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877 fNeedClose = false;
1878}
1879
1880SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1881 this->setPath(path, forceClose);
1882}
1883
1884void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 fPts = path.fPathRef->points();
1886 fVerbs = path.fPathRef->verbs();
1887 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001888 fConicWeights = path.fPathRef->conicWeights();
1889 if (fConicWeights) {
1890 fConicWeights -= 1; // begin one behind
1891 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001893 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 fForceClose = SkToU8(forceClose);
1895 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001896 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897}
1898
1899bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001900 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901 return false;
1902 }
1903 if (fForceClose) {
1904 return true;
1905 }
1906
1907 const uint8_t* verbs = fVerbs;
1908 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001909
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001910 if (kMove_Verb == *(verbs - 1)) {
1911 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 }
1913
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001914 while (verbs > stop) {
1915 // verbs points one beyond the current verb, decrement first.
1916 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 if (kMove_Verb == v) {
1918 break;
1919 }
1920 if (kClose_Verb == v) {
1921 return true;
1922 }
1923 }
1924 return false;
1925}
1926
1927SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001928 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001930 // A special case: if both points are NaN, SkPoint::operation== returns
1931 // false, but the iterator expects that they are treated as the same.
1932 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001933 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1934 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001935 return kClose_Verb;
1936 }
1937
reed@google.com9e25dbf2012-05-15 17:05:38 +00001938 pts[0] = fLastPt;
1939 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001940 fLastPt = fMoveTo;
1941 fCloseLine = true;
1942 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001943 } else {
1944 pts[0] = fMoveTo;
1945 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001947}
1948
reed@google.com9e25dbf2012-05-15 17:05:38 +00001949const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 if (fSegmentState == kAfterMove_SegmentState) {
1951 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001953 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001955
1956 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1957 // Set the first return pt to the last pt of the previous primitive.
1958 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959}
1960
caryclarke8c56662015-07-14 11:19:26 -07001961void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 // We need to step over anything that will not move the current draw point
1963 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001964 const uint8_t* lastMoveVerb = nullptr;
1965 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001966 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967 SkPoint lastPt = fLastPt;
1968 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001969 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 switch (verb) {
1971 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 // Keep a record of this most recent move
1973 lastMoveVerb = fVerbs;
1974 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001975 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001976 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001977 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 fPts++;
1979 break;
1980
1981 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001982 // A close when we are in a segment is always valid except when it
1983 // follows a move which follows a segment.
1984 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 return;
1986 }
1987 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001988 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001989 break;
1990
1991 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001992 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 if (lastMoveVerb) {
1994 fVerbs = lastMoveVerb;
1995 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001996 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 return;
1998 }
1999 return;
2000 }
2001 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002002 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003 fPts++;
2004 break;
2005
reed@google.com277c3f82013-05-31 15:17:50 +00002006 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002007 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002008 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002009 if (lastMoveVerb) {
2010 fVerbs = lastMoveVerb;
2011 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002012 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002013 return;
2014 }
2015 return;
2016 }
2017 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002018 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002019 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00002020 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002021 break;
2022
2023 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002024 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002025 if (lastMoveVerb) {
2026 fVerbs = lastMoveVerb;
2027 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002028 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002029 return;
2030 }
2031 return;
2032 }
2033 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002034 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002035 fPts += 3;
2036 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002037
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002038 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002039 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002040 }
2041 }
2042}
2043
reed@google.com4a3b7142012-05-16 17:16:46 +00002044SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002045 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002046
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002048 // Close the curve if requested and if there is some curve to close
2049 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002050 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 return kLine_Verb;
2052 }
2053 fNeedClose = false;
2054 return kClose_Verb;
2055 }
2056 return kDone_Verb;
2057 }
2058
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002059 // fVerbs is one beyond the current verb, decrement first
2060 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002061 const SkPoint* SK_RESTRICT srcPts = fPts;
2062 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063
2064 switch (verb) {
2065 case kMove_Verb:
2066 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002067 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068 verb = this->autoClose(pts);
2069 if (verb == kClose_Verb) {
2070 fNeedClose = false;
2071 }
2072 return (Verb)verb;
2073 }
2074 if (fVerbs == fVerbStop) { // might be a trailing moveto
2075 return kDone_Verb;
2076 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002077 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002078 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002080 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002081 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 fNeedClose = fForceClose;
2083 break;
2084 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002085 pts[0] = this->cons_moveTo();
2086 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002087 fLastPt = srcPts[0];
2088 fCloseLine = false;
2089 srcPts += 1;
2090 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002091 case kConic_Verb:
2092 fConicWeights += 1;
2093 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002094 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002095 pts[0] = this->cons_moveTo();
2096 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097 fLastPt = srcPts[1];
2098 srcPts += 2;
2099 break;
2100 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002101 pts[0] = this->cons_moveTo();
2102 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 fLastPt = srcPts[2];
2104 srcPts += 3;
2105 break;
2106 case kClose_Verb:
2107 verb = this->autoClose(pts);
2108 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002109 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110 } else {
2111 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002112 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002114 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 break;
2116 }
2117 fPts = srcPts;
2118 return (Verb)verb;
2119}
2120
2121///////////////////////////////////////////////////////////////////////////////
2122
Ben Wagner4d1955c2017-03-10 13:08:15 -05002123#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002124#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002125#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002126
reed@google.com51bbe752013-01-17 22:07:50 +00002127static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002128 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002129 str->append(label);
2130 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002131
reed@google.com51bbe752013-01-17 22:07:50 +00002132 const SkScalar* values = &pts[0].fX;
2133 count *= 2;
2134
2135 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002136 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002137 if (i < count - 1) {
2138 str->append(", ");
2139 }
2140 }
Mike Reed405b9d22018-01-09 15:11:08 -05002141 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002142 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002143 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002144 }
caryclark08fa28c2014-10-23 13:08:56 -07002145 str->append(");");
reede05fed02014-12-15 07:59:53 -08002146 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002147 str->append(" // ");
2148 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002149 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002150 if (i < count - 1) {
2151 str->append(", ");
2152 }
2153 }
2154 if (conicWeight >= 0) {
2155 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002156 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002157 }
2158 }
2159 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002160}
2161
caryclarke9562592014-09-15 09:26:09 -07002162void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002163 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164 Iter iter(*this, forceClose);
2165 SkPoint pts[4];
2166 Verb verb;
2167
reed@google.com51bbe752013-01-17 22:07:50 +00002168 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002169 char const * const gFillTypeStrs[] = {
2170 "Winding",
2171 "EvenOdd",
2172 "InverseWinding",
2173 "InverseEvenOdd",
2174 };
2175 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2176 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002177 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002178 switch (verb) {
2179 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002180 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002181 break;
2182 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002183 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002184 break;
2185 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002186 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002188 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002189 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002190 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002191 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002192 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 break;
2194 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002195 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196 break;
2197 default:
2198 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2199 verb = kDone_Verb; // stop the loop
2200 break;
2201 }
caryclark1049f122015-04-20 08:31:59 -07002202 if (!wStream && builder.size()) {
2203 SkDebugf("%s", builder.c_str());
2204 builder.reset();
2205 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002206 }
caryclark66a5d8b2014-06-24 08:30:15 -07002207 if (wStream) {
2208 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002209 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002210}
2211
reed@android.come522ca52009-11-23 20:10:41 +00002212void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002213 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002214}
2215
2216void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002217 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002218}
2219
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002220
Cary Clark0461e9f2017-08-25 15:13:38 -04002221bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002222 if ((fFillType & ~3) != 0) {
2223 return false;
2224 }
reed@google.comabf15c12011-01-18 20:35:51 +00002225
djsollen@google.com077348c2012-10-22 20:23:32 +00002226#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002227 if (!fBoundsIsDirty) {
2228 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002229
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002230 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002231 if (SkToBool(fIsFinite) != isFinite) {
2232 return false;
2233 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002234
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002235 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002236 // if we're empty, fBounds may be empty but translated, so we can't
2237 // necessarily compare to bounds directly
2238 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2239 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002240 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2241 return false;
2242 }
reed@android.come522ca52009-11-23 20:10:41 +00002243 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002244 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002245 if (!fBounds.isEmpty()) {
2246 return false;
2247 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002248 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002249 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002250 if (!fBounds.contains(bounds)) {
2251 return false;
2252 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002253 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002254 }
reed@android.come522ca52009-11-23 20:10:41 +00002255 }
2256 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002257#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002258 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002259}
reed@android.come522ca52009-11-23 20:10:41 +00002260
reed@google.com04863fa2011-05-15 04:08:24 +00002261///////////////////////////////////////////////////////////////////////////////
2262
reed@google.com0b7b9822011-05-16 12:29:27 +00002263static int sign(SkScalar x) { return x < 0; }
2264#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002265
robertphillipsc506e302014-09-16 09:43:31 -07002266enum DirChange {
2267 kLeft_DirChange,
2268 kRight_DirChange,
2269 kStraight_DirChange,
2270 kBackwards_DirChange,
2271
2272 kInvalid_DirChange
2273};
2274
2275
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002276static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002277 // The error epsilon was empirically derived; worse case round rects
2278 // with a mid point outset by 2x float epsilon in tests had an error
2279 // of 12.
2280 const int epsilon = 16;
2281 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2282 return false;
2283 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002284 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002285 int aBits = SkFloatAs2sCompliment(compA);
2286 int bBits = SkFloatAs2sCompliment(compB);
2287 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002288}
2289
caryclarkb4216502015-03-02 13:02:34 -08002290static bool approximately_zero_when_compared_to(double x, double y) {
2291 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002292}
2293
caryclarkb4216502015-03-02 13:02:34 -08002294
reed@google.com04863fa2011-05-15 04:08:24 +00002295// only valid for a single contour
2296struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002297 Convexicator()
2298 : fPtCount(0)
2299 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002300 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002301 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002302 , fIsCurve(false)
2303 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002304 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002305 // warnings
djsollen2f124632016-04-29 13:53:05 -07002306 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002307 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002308 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002309 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002310 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002311
2312 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002313 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002314 }
2315
2316 SkPath::Convexity getConvexity() const { return fConvexity; }
2317
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002318 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002319 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002320
reed@google.com04863fa2011-05-15 04:08:24 +00002321 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002322 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002323 return;
2324 }
2325
2326 if (0 == fPtCount) {
2327 fCurrPt = pt;
2328 ++fPtCount;
2329 } else {
2330 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002331 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002332 if (!SkScalarIsFinite(lengthSqd)) {
2333 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002334 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002335 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002336 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002337 fCurrPt = pt;
2338 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002339 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002340 } else {
2341 SkASSERT(fPtCount > 2);
2342 this->addVec(vec);
2343 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002344
reed@google.com85b6e392011-05-15 20:25:17 +00002345 int sx = sign(vec.fX);
2346 int sy = sign(vec.fY);
2347 fDx += (sx != fSx);
2348 fDy += (sy != fSy);
2349 fSx = sx;
2350 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002351
reed@google.com85b6e392011-05-15 20:25:17 +00002352 if (fDx > 3 || fDy > 3) {
2353 fConvexity = SkPath::kConcave_Convexity;
2354 }
reed@google.com04863fa2011-05-15 04:08:24 +00002355 }
2356 }
2357 }
2358
2359 void close() {
2360 if (fPtCount > 2) {
2361 this->addVec(fFirstVec);
2362 }
2363 }
2364
caryclarkb4216502015-03-02 13:02:34 -08002365 DirChange directionChange(const SkVector& curVec) {
2366 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2367
2368 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2369 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2370 largest = SkTMax(largest, -smallest);
2371
2372 if (!almost_equal(largest, largest + cross)) {
2373 int sign = SkScalarSignAsInt(cross);
2374 if (sign) {
2375 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2376 }
2377 }
2378
2379 if (cross) {
2380 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2381 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2382 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2383 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2384 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2385 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2386 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2387 if (sign) {
2388 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2389 }
2390 }
2391 }
2392
Cary Clarkdf429f32017-11-08 11:44:31 -05002393 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2394 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2395 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2396 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002397 fLastVec.dot(curVec) < 0.0f) {
2398 return kBackwards_DirChange;
2399 }
2400
2401 return kStraight_DirChange;
2402 }
2403
Cary Clarkc8323aa2017-08-25 08:04:43 -04002404 bool hasBackwards() const {
2405 return fBackwards;
2406 }
caryclarkb4216502015-03-02 13:02:34 -08002407
caryclarkd3d1a982014-12-08 04:57:38 -08002408 bool isFinite() const {
2409 return fIsFinite;
2410 }
2411
caryclark5ccef572015-03-02 10:07:56 -08002412 void setCurve(bool isCurve) {
2413 fIsCurve = isCurve;
2414 }
2415
reed@google.com04863fa2011-05-15 04:08:24 +00002416private:
2417 void addVec(const SkVector& vec) {
2418 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002419 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002420 switch (dir) {
2421 case kLeft_DirChange: // fall through
2422 case kRight_DirChange:
2423 if (kInvalid_DirChange == fExpectedDir) {
2424 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002425 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2426 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002427 } else if (dir != fExpectedDir) {
2428 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002429 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002430 }
2431 fLastVec = vec;
2432 break;
2433 case kStraight_DirChange:
2434 break;
2435 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002436 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002437 // If any of the subsequent dir is non-backward, it'll be concave.
2438 // Otherwise, it's still convex.
2439 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002440 }
robertphillipsc506e302014-09-16 09:43:31 -07002441 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002442 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002443 break;
2444 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002445 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002446 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002447 }
2448 }
2449
caryclarkb4216502015-03-02 13:02:34 -08002450 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002451 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002452 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002453 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2454 // value with the current vec is deemed to be of a significant value.
2455 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002456 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002457 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002458 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002459 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002460 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002461 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002462 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002463 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002464};
2465
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002466SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002467 // Sometimes we think we need to calculate convexity but another thread already did.
2468 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2469 return c;
2470 }
2471
reed@google.com04863fa2011-05-15 04:08:24 +00002472 SkPoint pts[4];
2473 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002474 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002475
2476 int contourCount = 0;
2477 int count;
2478 Convexicator state;
2479
caryclarkd3d1a982014-12-08 04:57:38 -08002480 if (!isFinite()) {
2481 return kUnknown_Convexity;
2482 }
Brian Osman205a1262017-09-18 13:13:48 +00002483 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002484 switch (verb) {
2485 case kMove_Verb:
2486 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002487 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002488 return kConcave_Convexity;
2489 }
2490 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002491 // fall through
2492 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002493 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002494 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002495 break;
caryclark5ccef572015-03-02 10:07:56 -08002496 case kQuad_Verb:
2497 // fall through
2498 case kConic_Verb:
2499 // fall through
2500 case kCubic_Verb:
2501 count = 2 + (kCubic_Verb == verb);
2502 // As an additional enhancement, this could set curve true only
2503 // if the curve is nonlinear
2504 state.setCurve(true);
2505 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002506 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002507 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002508 state.close();
2509 count = 0;
2510 break;
2511 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002512 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002513 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002514 return kConcave_Convexity;
2515 }
2516
2517 for (int i = 1; i <= count; i++) {
2518 state.addPt(pts[i]);
2519 }
2520 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002521 if (!state.isFinite()) {
2522 return kUnknown_Convexity;
2523 }
reed@google.com04863fa2011-05-15 04:08:24 +00002524 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002525 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002526 return kConcave_Convexity;
2527 }
2528 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002529 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002530 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002531 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2532 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2533 fConvexity = Convexity::kConcave_Convexity;
2534 } else {
2535 fFirstDirection = state.getFirstDirection();
2536 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002537 }
2538 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002539}
reed@google.com69a99432012-01-10 18:00:10 +00002540
2541///////////////////////////////////////////////////////////////////////////////
2542
2543class ContourIter {
2544public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002545 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002546
2547 bool done() const { return fDone; }
2548 // if !done() then these may be called
2549 int count() const { return fCurrPtCount; }
2550 const SkPoint* pts() const { return fCurrPt; }
2551 void next();
2552
2553private:
2554 int fCurrPtCount;
2555 const SkPoint* fCurrPt;
2556 const uint8_t* fCurrVerb;
2557 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002558 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002559 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002560 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002561};
2562
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002563ContourIter::ContourIter(const SkPathRef& pathRef) {
2564 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002565 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002566 fCurrPt = pathRef.points();
2567 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002568 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002569 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002570 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002571 this->next();
2572}
2573
2574void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002575 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002576 fDone = true;
2577 }
2578 if (fDone) {
2579 return;
2580 }
2581
2582 // skip pts of prev contour
2583 fCurrPt += fCurrPtCount;
2584
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002585 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002586 int ptCount = 1; // moveTo
2587 const uint8_t* verbs = fCurrVerb;
2588
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002589 for (--verbs; verbs > fStopVerbs; --verbs) {
2590 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002591 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002592 goto CONTOUR_END;
2593 case SkPath::kLine_Verb:
2594 ptCount += 1;
2595 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002596 case SkPath::kConic_Verb:
2597 fCurrConicWeight += 1;
2598 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002599 case SkPath::kQuad_Verb:
2600 ptCount += 2;
2601 break;
2602 case SkPath::kCubic_Verb:
2603 ptCount += 3;
2604 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002605 case SkPath::kClose_Verb:
2606 break;
2607 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002608 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002609 break;
2610 }
2611 }
2612CONTOUR_END:
2613 fCurrPtCount = ptCount;
2614 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002615 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002616}
2617
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002618// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002619static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002620 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2621 // We may get 0 when the above subtracts underflow. We expect this to be
2622 // very rare and lazily promote to double.
2623 if (0 == cross) {
2624 double p0x = SkScalarToDouble(p0.fX);
2625 double p0y = SkScalarToDouble(p0.fY);
2626
2627 double p1x = SkScalarToDouble(p1.fX);
2628 double p1y = SkScalarToDouble(p1.fY);
2629
2630 double p2x = SkScalarToDouble(p2.fX);
2631 double p2y = SkScalarToDouble(p2.fY);
2632
2633 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2634 (p1y - p0y) * (p2x - p0x));
2635
2636 }
2637 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002638}
2639
reed@google.comc1ea60a2012-01-31 15:15:36 +00002640// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002641static int find_max_y(const SkPoint pts[], int count) {
2642 SkASSERT(count > 0);
2643 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002644 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002645 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002646 SkScalar y = pts[i].fY;
2647 if (y > max) {
2648 max = y;
2649 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002650 }
2651 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002652 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002653}
2654
reed@google.comcabaf1d2012-01-11 21:03:05 +00002655static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2656 int i = index;
2657 for (;;) {
2658 i = (i + inc) % n;
2659 if (i == index) { // we wrapped around, so abort
2660 break;
2661 }
2662 if (pts[index] != pts[i]) { // found a different point, success!
2663 break;
2664 }
2665 }
2666 return i;
2667}
2668
reed@google.comc1ea60a2012-01-31 15:15:36 +00002669/**
2670 * Starting at index, and moving forward (incrementing), find the xmin and
2671 * xmax of the contiguous points that have the same Y.
2672 */
2673static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2674 int* maxIndexPtr) {
2675 const SkScalar y = pts[index].fY;
2676 SkScalar min = pts[index].fX;
2677 SkScalar max = min;
2678 int minIndex = index;
2679 int maxIndex = index;
2680 for (int i = index + 1; i < n; ++i) {
2681 if (pts[i].fY != y) {
2682 break;
2683 }
2684 SkScalar x = pts[i].fX;
2685 if (x < min) {
2686 min = x;
2687 minIndex = i;
2688 } else if (x > max) {
2689 max = x;
2690 maxIndex = i;
2691 }
2692 }
2693 *maxIndexPtr = maxIndex;
2694 return minIndex;
2695}
2696
reed026beb52015-06-10 14:23:15 -07002697static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2698 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002699}
2700
reed@google.comac8543f2012-01-30 20:51:25 +00002701/*
2702 * We loop through all contours, and keep the computed cross-product of the
2703 * contour that contained the global y-max. If we just look at the first
2704 * contour, we may find one that is wound the opposite way (correctly) since
2705 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2706 * that is outer most (or at least has the global y-max) before we can consider
2707 * its cross product.
2708 */
reed026beb52015-06-10 14:23:15 -07002709bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002710 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2711 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002712 return true;
2713 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002714
2715 // don't want to pay the cost for computing this if it
2716 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002717 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2718 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002719 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002720 return false;
2721 }
reed@google.com69a99432012-01-10 18:00:10 +00002722
reed026beb52015-06-10 14:23:15 -07002723 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002724
reed@google.comac8543f2012-01-30 20:51:25 +00002725 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002726 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002727 SkScalar ymaxCross = 0;
2728
reed@google.com69a99432012-01-10 18:00:10 +00002729 for (; !iter.done(); iter.next()) {
2730 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002731 if (n < 3) {
2732 continue;
2733 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002734
reed@google.comcabaf1d2012-01-11 21:03:05 +00002735 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002736 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002737 int index = find_max_y(pts, n);
2738 if (pts[index].fY < ymax) {
2739 continue;
2740 }
2741
2742 // If there is more than 1 distinct point at the y-max, we take the
2743 // x-min and x-max of them and just subtract to compute the dir.
2744 if (pts[(index + 1) % n].fY == pts[index].fY) {
2745 int maxIndex;
2746 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2747 if (minIndex == maxIndex) {
2748 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002749 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002750 SkASSERT(pts[minIndex].fY == pts[index].fY);
2751 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2752 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2753 // we just subtract the indices, and let that auto-convert to
2754 // SkScalar, since we just want - or + to signal the direction.
2755 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002756 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002757 TRY_CROSSPROD:
2758 // Find a next and prev index to use for the cross-product test,
2759 // but we try to find pts that form non-zero vectors from pts[index]
2760 //
2761 // Its possible that we can't find two non-degenerate vectors, so
2762 // we have to guard our search (e.g. all the pts could be in the
2763 // same place).
2764
2765 // we pass n - 1 instead of -1 so we don't foul up % operator by
2766 // passing it a negative LH argument.
2767 int prev = find_diff_pt(pts, index, n, n - 1);
2768 if (prev == index) {
2769 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002770 continue;
2771 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002772 int next = find_diff_pt(pts, index, n, 1);
2773 SkASSERT(next != index);
2774 cross = cross_prod(pts[prev], pts[index], pts[next]);
2775 // if we get a zero and the points are horizontal, then we look at the spread in
2776 // x-direction. We really should continue to walk away from the degeneracy until
2777 // there is a divergence.
2778 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2779 // construct the subtract so we get the correct Direction below
2780 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002781 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002782 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002783
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002784 if (cross) {
2785 // record our best guess so far
2786 ymax = pts[index].fY;
2787 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002788 }
2789 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002790 if (ymaxCross) {
2791 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002792 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002793 return true;
2794 } else {
2795 return false;
2796 }
reed@google.comac8543f2012-01-30 20:51:25 +00002797}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002798
2799///////////////////////////////////////////////////////////////////////////////
2800
caryclark9aacd902015-12-14 08:38:09 -08002801static bool between(SkScalar a, SkScalar b, SkScalar c) {
2802 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2803 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2804 return (a - b) * (c - b) <= 0;
2805}
2806
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2808 SkScalar t) {
2809 SkScalar A = c3 + 3*(c1 - c2) - c0;
2810 SkScalar B = 3*(c2 - c1 - c1 + c0);
2811 SkScalar C = 3*(c1 - c0);
2812 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002813 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002814}
2815
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816template <size_t N> static void find_minmax(const SkPoint pts[],
2817 SkScalar* minPtr, SkScalar* maxPtr) {
2818 SkScalar min, max;
2819 min = max = pts[0].fX;
2820 for (size_t i = 1; i < N; ++i) {
2821 min = SkMinScalar(min, pts[i].fX);
2822 max = SkMaxScalar(max, pts[i].fX);
2823 }
2824 *minPtr = min;
2825 *maxPtr = max;
2826}
2827
caryclark9cb5d752015-12-18 04:35:24 -08002828static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2829 if (start.fY == end.fY) {
2830 return between(start.fX, x, end.fX) && x != end.fX;
2831 } else {
2832 return x == start.fX && y == start.fY;
2833 }
2834}
2835
caryclark9aacd902015-12-14 08:38:09 -08002836static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002837 SkScalar y0 = pts[0].fY;
2838 SkScalar y3 = pts[3].fY;
2839
2840 int dir = 1;
2841 if (y0 > y3) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002842 using std::swap;
2843 swap(y0, y3);
caryclark9cb5d752015-12-18 04:35:24 -08002844 dir = -1;
2845 }
2846 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002847 return 0;
2848 }
caryclark9cb5d752015-12-18 04:35:24 -08002849 if (checkOnCurve(x, y, pts[0], pts[3])) {
2850 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002851 return 0;
2852 }
caryclark9cb5d752015-12-18 04:35:24 -08002853 if (y == y3) {
2854 return 0;
2855 }
2856
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002857 // quickreject or quickaccept
2858 SkScalar min, max;
2859 find_minmax<4>(pts, &min, &max);
2860 if (x < min) {
2861 return 0;
2862 }
2863 if (x > max) {
2864 return dir;
2865 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002866
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002868 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002869 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002870 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002871 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002872 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002873 if (SkScalarNearlyEqual(xt, x)) {
2874 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2875 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002876 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002877 }
2878 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 return xt < x ? dir : 0;
2880}
2881
caryclark9aacd902015-12-14 08:38:09 -08002882static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002883 SkPoint dst[10];
2884 int n = SkChopCubicAtYExtrema(pts, dst);
2885 int w = 0;
2886 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002887 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 }
2889 return w;
2890}
2891
caryclark9aacd902015-12-14 08:38:09 -08002892static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2893 SkASSERT(src);
2894 SkASSERT(t >= 0 && t <= 1);
2895 SkScalar src2w = src[2] * w;
2896 SkScalar C = src[0];
2897 SkScalar A = src[4] - 2 * src2w + C;
2898 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002899 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002900}
2901
2902
2903static double conic_eval_denominator(SkScalar w, SkScalar t) {
2904 SkScalar B = 2 * (w - 1);
2905 SkScalar C = 1;
2906 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002907 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002908}
2909
2910static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2911 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002912 SkScalar y0 = pts[0].fY;
2913 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002914
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002915 int dir = 1;
2916 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002917 using std::swap;
2918 swap(y0, y2);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002919 dir = -1;
2920 }
caryclark9aacd902015-12-14 08:38:09 -08002921 if (y < y0 || y > y2) {
2922 return 0;
2923 }
caryclark9cb5d752015-12-18 04:35:24 -08002924 if (checkOnCurve(x, y, pts[0], pts[2])) {
2925 *onCurveCount += 1;
2926 return 0;
2927 }
caryclark9aacd902015-12-14 08:38:09 -08002928 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002929 return 0;
2930 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002931
caryclark9aacd902015-12-14 08:38:09 -08002932 SkScalar roots[2];
2933 SkScalar A = pts[2].fY;
2934 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2935 SkScalar C = pts[0].fY;
2936 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2937 B -= C; // B = b*w - w * yCept + yCept - a
2938 C -= y;
2939 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2940 SkASSERT(n <= 1);
2941 SkScalar xt;
2942 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002943 // zero roots are returned only when y0 == y
2944 // Need [0] if dir == 1
2945 // and [2] if dir == -1
2946 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002947 } else {
2948 SkScalar t = roots[0];
2949 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2950 }
2951 if (SkScalarNearlyEqual(xt, x)) {
2952 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2953 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002954 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002955 }
2956 }
2957 return xt < x ? dir : 0;
2958}
2959
2960static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2961 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2962 if (y0 == y1) {
2963 return true;
2964 }
2965 if (y0 < y1) {
2966 return y1 <= y2;
2967 } else {
2968 return y1 >= y2;
2969 }
2970}
2971
2972static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2973 int* onCurveCount) {
2974 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002975 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002976 // If the data points are very large, the conic may not be monotonic but may also
2977 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002978 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2979 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2980 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002981 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2982 }
2983 return w;
2984}
2985
2986static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2987 SkScalar y0 = pts[0].fY;
2988 SkScalar y2 = pts[2].fY;
2989
2990 int dir = 1;
2991 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002992 using std::swap;
2993 swap(y0, y2);
caryclark9aacd902015-12-14 08:38:09 -08002994 dir = -1;
2995 }
2996 if (y < y0 || y > y2) {
2997 return 0;
2998 }
caryclark9cb5d752015-12-18 04:35:24 -08002999 if (checkOnCurve(x, y, pts[0], pts[2])) {
3000 *onCurveCount += 1;
3001 return 0;
3002 }
caryclark9aacd902015-12-14 08:38:09 -08003003 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003004 return 0;
3005 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 // bounds check on X (not required. is it faster?)
3007#if 0
3008 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3009 return 0;
3010 }
3011#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003012
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 SkScalar roots[2];
3014 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3015 2 * (pts[1].fY - pts[0].fY),
3016 pts[0].fY - y,
3017 roots);
3018 SkASSERT(n <= 1);
3019 SkScalar xt;
3020 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003021 // zero roots are returned only when y0 == y
3022 // Need [0] if dir == 1
3023 // and [2] if dir == -1
3024 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003025 } else {
3026 SkScalar t = roots[0];
3027 SkScalar C = pts[0].fX;
3028 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3029 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003030 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 }
caryclark9aacd902015-12-14 08:38:09 -08003032 if (SkScalarNearlyEqual(xt, x)) {
3033 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3034 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003035 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003036 }
3037 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003038 return xt < x ? dir : 0;
3039}
3040
caryclark9aacd902015-12-14 08:38:09 -08003041static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003042 SkPoint dst[5];
3043 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003044
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003045 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3046 n = SkChopQuadAtYExtrema(pts, dst);
3047 pts = dst;
3048 }
caryclark9aacd902015-12-14 08:38:09 -08003049 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003050 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003051 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003052 }
3053 return w;
3054}
3055
caryclark9aacd902015-12-14 08:38:09 -08003056static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003057 SkScalar x0 = pts[0].fX;
3058 SkScalar y0 = pts[0].fY;
3059 SkScalar x1 = pts[1].fX;
3060 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003061
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003062 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003063
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003064 int dir = 1;
3065 if (y0 > y1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04003066 using std::swap;
3067 swap(y0, y1);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003068 dir = -1;
3069 }
caryclark9aacd902015-12-14 08:38:09 -08003070 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003071 return 0;
3072 }
caryclark9cb5d752015-12-18 04:35:24 -08003073 if (checkOnCurve(x, y, pts[0], pts[1])) {
3074 *onCurveCount += 1;
3075 return 0;
3076 }
3077 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003078 return 0;
3079 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003080 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003081
caryclark9aacd902015-12-14 08:38:09 -08003082 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003083 // zero cross means the point is on the line, and since the case where
3084 // y of the query point is at the end point is handled above, we can be
3085 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003086 if (x != x1 || y != pts[1].fY) {
3087 *onCurveCount += 1;
3088 }
caryclark9aacd902015-12-14 08:38:09 -08003089 dir = 0;
3090 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003091 dir = 0;
3092 }
3093 return dir;
3094}
3095
caryclark9aacd902015-12-14 08:38:09 -08003096static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3097 SkTDArray<SkVector>* tangents) {
3098 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3099 && !between(pts[2].fY, y, pts[3].fY)) {
3100 return;
3101 }
3102 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3103 && !between(pts[2].fX, x, pts[3].fX)) {
3104 return;
3105 }
3106 SkPoint dst[10];
3107 int n = SkChopCubicAtYExtrema(pts, dst);
3108 for (int i = 0; i <= n; ++i) {
3109 SkPoint* c = &dst[i * 3];
3110 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003111 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003112 continue;
mbarbella276e6332016-05-31 14:44:01 -07003113 }
caryclark9aacd902015-12-14 08:38:09 -08003114 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3115 if (!SkScalarNearlyEqual(x, xt)) {
3116 continue;
3117 }
3118 SkVector tangent;
3119 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
Mike Reed5edcd312018-08-08 11:23:41 -04003120 tangents->push_back(tangent);
caryclark9aacd902015-12-14 08:38:09 -08003121 }
3122}
3123
3124static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3125 SkTDArray<SkVector>* tangents) {
3126 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3127 return;
3128 }
3129 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3130 return;
3131 }
3132 SkScalar roots[2];
3133 SkScalar A = pts[2].fY;
3134 SkScalar B = pts[1].fY * w - y * w + y;
3135 SkScalar C = pts[0].fY;
3136 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3137 B -= C; // B = b*w - w * yCept + yCept - a
3138 C -= y;
3139 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3140 for (int index = 0; index < n; ++index) {
3141 SkScalar t = roots[index];
3142 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3143 if (!SkScalarNearlyEqual(x, xt)) {
3144 continue;
3145 }
3146 SkConic conic(pts, w);
Mike Reed5edcd312018-08-08 11:23:41 -04003147 tangents->push_back(conic.evalTangentAt(t));
caryclark9aacd902015-12-14 08:38:09 -08003148 }
3149}
3150
3151static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3152 SkTDArray<SkVector>* tangents) {
3153 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3154 return;
3155 }
3156 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3157 return;
3158 }
3159 SkScalar roots[2];
3160 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3161 2 * (pts[1].fY - pts[0].fY),
3162 pts[0].fY - y,
3163 roots);
3164 for (int index = 0; index < n; ++index) {
3165 SkScalar t = roots[index];
3166 SkScalar C = pts[0].fX;
3167 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3168 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003169 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003170 if (!SkScalarNearlyEqual(x, xt)) {
3171 continue;
3172 }
Mike Reed5edcd312018-08-08 11:23:41 -04003173 tangents->push_back(SkEvalQuadTangentAt(pts, t));
caryclark9aacd902015-12-14 08:38:09 -08003174 }
3175}
3176
3177static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3178 SkTDArray<SkVector>* tangents) {
3179 SkScalar y0 = pts[0].fY;
3180 SkScalar y1 = pts[1].fY;
3181 if (!between(y0, y, y1)) {
3182 return;
3183 }
3184 SkScalar x0 = pts[0].fX;
3185 SkScalar x1 = pts[1].fX;
3186 if (!between(x0, x, x1)) {
3187 return;
3188 }
3189 SkScalar dx = x1 - x0;
3190 SkScalar dy = y1 - y0;
3191 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3192 return;
3193 }
3194 SkVector v;
3195 v.set(dx, dy);
Mike Reed5edcd312018-08-08 11:23:41 -04003196 tangents->push_back(v);
caryclark9aacd902015-12-14 08:38:09 -08003197}
3198
reed@google.com4db592c2013-10-30 17:39:43 +00003199static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3200 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3201}
3202
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003203bool SkPath::contains(SkScalar x, SkScalar y) const {
3204 bool isInverse = this->isInverseFillType();
3205 if (this->isEmpty()) {
3206 return isInverse;
3207 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003208
reed@google.com4db592c2013-10-30 17:39:43 +00003209 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003210 return isInverse;
3211 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003212
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003213 SkPath::Iter iter(*this, true);
3214 bool done = false;
3215 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003216 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003217 do {
3218 SkPoint pts[4];
3219 switch (iter.next(pts, false)) {
3220 case SkPath::kMove_Verb:
3221 case SkPath::kClose_Verb:
3222 break;
3223 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003224 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003225 break;
3226 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003227 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003228 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003229 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003230 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003231 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003232 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003233 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003234 break;
3235 case SkPath::kDone_Verb:
3236 done = true;
3237 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003238 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003239 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003240 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3241 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3242 if (evenOddFill) {
3243 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003244 }
caryclark9aacd902015-12-14 08:38:09 -08003245 if (w) {
3246 return !isInverse;
3247 }
3248 if (onCurveCount <= 1) {
3249 return SkToBool(onCurveCount) ^ isInverse;
3250 }
3251 if ((onCurveCount & 1) || evenOddFill) {
3252 return SkToBool(onCurveCount & 1) ^ isInverse;
3253 }
halcanary9d524f22016-03-29 09:03:52 -07003254 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003255 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3256 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003257 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003258 SkTDArray<SkVector> tangents;
3259 do {
3260 SkPoint pts[4];
3261 int oldCount = tangents.count();
3262 switch (iter.next(pts, false)) {
3263 case SkPath::kMove_Verb:
3264 case SkPath::kClose_Verb:
3265 break;
3266 case SkPath::kLine_Verb:
3267 tangent_line(pts, x, y, &tangents);
3268 break;
3269 case SkPath::kQuad_Verb:
3270 tangent_quad(pts, x, y, &tangents);
3271 break;
3272 case SkPath::kConic_Verb:
3273 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3274 break;
3275 case SkPath::kCubic_Verb:
3276 tangent_cubic(pts, x, y, &tangents);
3277 break;
3278 case SkPath::kDone_Verb:
3279 done = true;
3280 break;
3281 }
3282 if (tangents.count() > oldCount) {
3283 int last = tangents.count() - 1;
3284 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003285 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003286 tangents.remove(last);
3287 } else {
3288 for (int index = 0; index < last; ++index) {
3289 const SkVector& test = tangents[index];
3290 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003291 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3292 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003293 tangents.remove(last);
3294 tangents.removeShuffle(index);
3295 break;
3296 }
3297 }
3298 }
3299 }
3300 } while (!done);
3301 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003302}
fmalitaaa0df4e2015-12-01 09:13:23 -08003303
3304int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3305 SkScalar w, SkPoint pts[], int pow2) {
3306 const SkConic conic(p0, p1, p2, w);
3307 return conic.chopIntoQuadsPOW2(pts, pow2);
3308}
bsalomonedc743a2016-06-01 09:42:31 -07003309
3310bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3311 unsigned* start) {
3312 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3313 return false;
3314 }
3315 SkPath::RawIter iter(path);
3316 SkPoint verbPts[4];
3317 SkPath::Verb v;
3318 SkPoint rectPts[5];
3319 int rectPtCnt = 0;
3320 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3321 switch (v) {
3322 case SkPath::kMove_Verb:
3323 if (0 != rectPtCnt) {
3324 return false;
3325 }
3326 rectPts[0] = verbPts[0];
3327 ++rectPtCnt;
3328 break;
3329 case SkPath::kLine_Verb:
3330 if (5 == rectPtCnt) {
3331 return false;
3332 }
3333 rectPts[rectPtCnt] = verbPts[1];
3334 ++rectPtCnt;
3335 break;
3336 case SkPath::kClose_Verb:
3337 if (4 == rectPtCnt) {
3338 rectPts[4] = rectPts[0];
3339 rectPtCnt = 5;
3340 }
3341 break;
3342 default:
3343 return false;
3344 }
3345 }
3346 if (rectPtCnt < 5) {
3347 return false;
3348 }
3349 if (rectPts[0] != rectPts[4]) {
3350 return false;
3351 }
bsalomon057ae8a2016-07-24 05:37:26 -07003352 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3353 // and pts 1 and 2 the opposite vertical or horizontal edge).
3354 bool vec03IsVertical;
3355 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3356 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3357 // Make sure it has non-zero width and height
3358 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003359 return false;
3360 }
bsalomon057ae8a2016-07-24 05:37:26 -07003361 vec03IsVertical = true;
3362 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3363 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3364 // Make sure it has non-zero width and height
3365 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3366 return false;
3367 }
3368 vec03IsVertical = false;
3369 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003370 return false;
3371 }
bsalomon057ae8a2016-07-24 05:37:26 -07003372 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3373 // set if it is on the bottom edge.
3374 unsigned sortFlags =
3375 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3376 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3377 switch (sortFlags) {
3378 case 0b00:
3379 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3380 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3381 *start = 0;
3382 break;
3383 case 0b01:
3384 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3385 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3386 *start = 1;
3387 break;
3388 case 0b10:
3389 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3390 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3391 *start = 3;
3392 break;
3393 case 0b11:
3394 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3395 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3396 *start = 2;
3397 break;
bsalomonedc743a2016-06-01 09:42:31 -07003398 }
bsalomonedc743a2016-06-01 09:42:31 -07003399 return true;
3400}
bsalomon21af9ca2016-08-25 12:29:23 -07003401
Brian Salomone4949402018-04-26 15:22:04 -04003402bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3403 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3404 // This gets converted to an oval.
3405 return true;
3406 }
3407 if (useCenter) {
3408 // This is a pie wedge. It's convex if the angle is <= 180.
3409 return SkScalarAbs(sweepAngle) <= 180.f;
3410 }
3411 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3412 // to a secant, i.e. convex.
3413 return SkScalarAbs(sweepAngle) <= 360.f;
3414}
3415
bsalomon21af9ca2016-08-25 12:29:23 -07003416void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3417 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3418 SkASSERT(!oval.isEmpty());
3419 SkASSERT(sweepAngle);
3420
3421 path->reset();
3422 path->setIsVolatile(true);
3423 path->setFillType(SkPath::kWinding_FillType);
3424 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3425 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003426 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003427 return;
3428 }
3429 if (useCenter) {
3430 path->moveTo(oval.centerX(), oval.centerY());
3431 }
Brian Salomone4949402018-04-26 15:22:04 -04003432 auto firstDir =
3433 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3434 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003435 // Arc to mods at 360 and drawArc is not supposed to.
3436 bool forceMoveTo = !useCenter;
3437 while (sweepAngle <= -360.f) {
3438 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3439 startAngle -= 180.f;
3440 path->arcTo(oval, startAngle, -180.f, false);
3441 startAngle -= 180.f;
3442 forceMoveTo = false;
3443 sweepAngle += 360.f;
3444 }
3445 while (sweepAngle >= 360.f) {
3446 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3447 startAngle += 180.f;
3448 path->arcTo(oval, startAngle, 180.f, false);
3449 startAngle += 180.f;
3450 forceMoveTo = false;
3451 sweepAngle -= 360.f;
3452 }
3453 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3454 if (useCenter) {
3455 path->close();
3456 }
Brian Salomone4949402018-04-26 15:22:04 -04003457 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3458 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003459}
Mike Reed0d7dac82017-02-02 17:45:56 -08003460
3461///////////////////////////////////////////////////////////////////////////////////////////////////
3462#include "SkNx.h"
3463
3464static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3465 SkScalar ts[2];
3466 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3467 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3468 SkASSERT(n >= 0 && n <= 2);
3469 for (int i = 0; i < n; ++i) {
3470 extremas[i] = SkEvalQuadAt(src, ts[i]);
3471 }
3472 extremas[n] = src[2];
3473 return n + 1;
3474}
3475
3476static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3477 SkConic conic(src[0], src[1], src[2], w);
3478 SkScalar ts[2];
3479 int n = conic.findXExtrema(ts);
3480 n += conic.findYExtrema(&ts[n]);
3481 SkASSERT(n >= 0 && n <= 2);
3482 for (int i = 0; i < n; ++i) {
3483 extremas[i] = conic.evalAt(ts[i]);
3484 }
3485 extremas[n] = src[2];
3486 return n + 1;
3487}
3488
3489static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3490 SkScalar ts[4];
3491 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3492 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3493 SkASSERT(n >= 0 && n <= 4);
3494 for (int i = 0; i < n; ++i) {
3495 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3496 }
3497 extremas[n] = src[3];
3498 return n + 1;
3499}
3500
Mike Reed8d3196b2017-02-03 11:34:13 -05003501SkRect SkPath::computeTightBounds() const {
3502 if (0 == this->countVerbs()) {
3503 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003504 }
3505
Mike Reed8d3196b2017-02-03 11:34:13 -05003506 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3507 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003508 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003509
Mike Reed0d7dac82017-02-02 17:45:56 -08003510 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3511 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003512 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003513
3514 // initial with the first MoveTo, so we don't have to check inside the switch
3515 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003516 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003517 for (;;) {
3518 int count = 0;
3519 switch (iter.next(pts)) {
3520 case SkPath::kMove_Verb:
3521 extremas[0] = pts[0];
3522 count = 1;
3523 break;
3524 case SkPath::kLine_Verb:
3525 extremas[0] = pts[1];
3526 count = 1;
3527 break;
3528 case SkPath::kQuad_Verb:
3529 count = compute_quad_extremas(pts, extremas);
3530 break;
3531 case SkPath::kConic_Verb:
3532 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3533 break;
3534 case SkPath::kCubic_Verb:
3535 count = compute_cubic_extremas(pts, extremas);
3536 break;
3537 case SkPath::kClose_Verb:
3538 break;
3539 case SkPath::kDone_Verb:
3540 goto DONE;
3541 }
3542 for (int i = 0; i < count; ++i) {
3543 Sk2s tmp = from_point(extremas[i]);
3544 min = Sk2s::Min(min, tmp);
3545 max = Sk2s::Max(max, tmp);
3546 }
3547 }
3548DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003549 SkRect bounds;
3550 min.store((SkPoint*)&bounds.fLeft);
3551 max.store((SkPoint*)&bounds.fRight);
3552 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003553}
Cary Clarkdf429f32017-11-08 11:44:31 -05003554
3555bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3556 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3557}
3558
3559bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3560 const SkPoint& p3, bool exact) {
3561 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3562 SkPointPriv::EqualsWithinTolerance(p2, p3);
3563}
3564
3565bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3566 const SkPoint& p3, const SkPoint& p4, bool exact) {
3567 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3568 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3569 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3570 SkPointPriv::EqualsWithinTolerance(p3, p4);
3571}