blob: 2808c12ed4e8b544c8957011b0400b57a2c5a2f0 [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#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001467 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001468#else
1469 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1470 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1471#endif
caryclark55d49052016-01-23 05:07:04 -08001472 SkScalar thetaWidth = thetaArc / segments;
1473 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1474 if (!SkScalarIsFinite(t)) {
Mike Reedb6317422018-08-15 10:23:39 -04001475 return *this;
caryclark55d49052016-01-23 05:07:04 -08001476 }
1477 SkScalar startTheta = theta1;
1478 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001479#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1480 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1481 return scalar == SkScalarFloorToScalar(scalar);
1482 };
1483 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1484 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1485 scalar_is_integer(x) && scalar_is_integer(y);
1486#endif
caryclark55d49052016-01-23 05:07:04 -08001487 for (int i = 0; i < segments; ++i) {
1488 SkScalar endTheta = startTheta + thetaWidth;
1489 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1490
1491 unitPts[1].set(cosEndTheta, sinEndTheta);
1492 unitPts[1] += centerPoint;
1493 unitPts[0] = unitPts[1];
1494 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1495 SkPoint mapped[2];
1496 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001497 /*
1498 Computing the arc width introduces rounding errors that cause arcs to start
1499 outside their marks. A round rect may lose convexity as a result. If the input
1500 values are on integers, place the conic on integers as well.
1501 */
1502#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1503 if (expectIntegers) {
1504 SkScalar* mappedScalars = &mapped[0].fX;
1505 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1506 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1507 }
1508 }
1509#endif
caryclark55d49052016-01-23 05:07:04 -08001510 this->conicTo(mapped[0], mapped[1], w);
1511 startTheta = endTheta;
1512 }
Mike Reedb6317422018-08-15 10:23:39 -04001513 return *this;
caryclark55d49052016-01-23 05:07:04 -08001514}
1515
Mike Reedb6317422018-08-15 10:23:39 -04001516SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1517 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
caryclark55d49052016-01-23 05:07:04 -08001518 SkPoint currentPoint;
1519 this->getLastPt(&currentPoint);
Mike Reedb6317422018-08-15 10:23:39 -04001520 return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
1521 currentPoint.fX + dx, currentPoint.fY + dy);
caryclark55d49052016-01-23 05:07:04 -08001522}
1523
Mike Reedb6317422018-08-15 10:23:39 -04001524SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 if (oval.isEmpty() || 0 == sweepAngle) {
Mike Reedb6317422018-08-15 10:23:39 -04001526 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 }
1528
1529 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1530
1531 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001532 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1533 // See SkPath::addOval() docs.
1534 SkScalar startOver90 = startAngle / 90.f;
1535 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1536 SkScalar error = startOver90 - startOver90I;
1537 if (SkScalarNearlyEqual(error, 0)) {
1538 // Index 1 is at startAngle == 0.
1539 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1540 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
Mike Reedb6317422018-08-15 10:23:39 -04001541 return this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1542 (unsigned) startIndex);
bsalomon1978ce22016-05-31 10:42:16 -07001543 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 }
Mike Reedb6317422018-08-15 10:23:39 -04001545 return this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546}
1547
1548/*
1549 Need to handle the case when the angle is sharp, and our computed end-points
1550 for the arc go behind pt1 and/or p2...
1551*/
Mike Reedb6317422018-08-15 10:23:39 -04001552SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001553 if (radius == 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001554 return this->lineTo(x1, y1);
reeda8b326c2014-12-09 11:50:32 -08001555 }
1556
1557 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001558
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 // need to know our prev pt so we can construct tangent vectors
1560 {
1561 SkPoint start;
1562 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001563 // Handle degenerate cases by adding a line to the first point and
1564 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 before.setNormalize(x1 - start.fX, y1 - start.fY);
1566 after.setNormalize(x2 - x1, y2 - y1);
1567 }
reed@google.comabf15c12011-01-18 20:35:51 +00001568
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 SkScalar cosh = SkPoint::DotProduct(before, after);
1570 SkScalar sinh = SkPoint::CrossProduct(before, after);
1571
1572 if (SkScalarNearlyZero(sinh)) { // angle is too tight
Mike Reedb6317422018-08-15 10:23:39 -04001573 return this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 }
reed@google.comabf15c12011-01-18 20:35:51 +00001575
Mike Reeda99b6ce2017-02-04 11:04:26 -05001576 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577
Mike Reeda99b6ce2017-02-04 11:04:26 -05001578 SkScalar xx = x1 - dist * before.fX;
1579 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001580 after.setLength(dist);
1581 this->lineTo(xx, yy);
1582 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
Mike Reedb6317422018-08-15 10:23:39 -04001583 return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584}
1585
1586///////////////////////////////////////////////////////////////////////////////
1587
Mike Reedb6317422018-08-15 10:23:39 -04001588SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 SkMatrix matrix;
1590
1591 matrix.setTranslate(dx, dy);
Mike Reedb6317422018-08-15 10:23:39 -04001592 return this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001593}
1594
Mike Reedc3d8a482018-09-12 10:08:40 -04001595SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) {
1596 // Detect if we're trying to add ourself
1597 const SkPath* src = &srcPath;
1598 SkTLazy<SkPath> tmp;
1599 if (this == src) {
1600 src = tmp.set(srcPath);
1601 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602
Mike Reedc3d8a482018-09-12 10:08:40 -04001603 SkPathRef::Editor(&fPathRef, src->countVerbs(), src->countPoints());
1604
1605 RawIter iter(*src);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 SkPoint pts[4];
1607 Verb verb;
1608
Cary Clark9480d822017-10-19 18:01:13 -04001609 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001610 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 while ((verb = iter.next(pts)) != kDone_Verb) {
1612 switch (verb) {
1613 case kMove_Verb:
1614 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001615 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1616 injectMoveToIfNeeded(); // In case last contour is closed
Cary Clark54ff3022018-08-17 10:58:23 -04001617 SkPoint lastPt;
1618 // don't add lineTo if it is degenerate
1619 if (fLastMoveToIndex < 0 || !this->getLastPt(&lastPt) || lastPt != pts[0]) {
1620 this->lineTo(pts[0]);
1621 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001622 } else {
1623 this->moveTo(pts[0]);
1624 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625 break;
1626 case kLine_Verb:
1627 proc(matrix, &pts[1], &pts[1], 1);
1628 this->lineTo(pts[1]);
1629 break;
1630 case kQuad_Verb:
1631 proc(matrix, &pts[1], &pts[1], 2);
1632 this->quadTo(pts[1], pts[2]);
1633 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001634 case kConic_Verb:
1635 proc(matrix, &pts[1], &pts[1], 2);
1636 this->conicTo(pts[1], pts[2], iter.conicWeight());
1637 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 case kCubic_Verb:
1639 proc(matrix, &pts[1], &pts[1], 3);
1640 this->cubicTo(pts[1], pts[2], pts[3]);
1641 break;
1642 case kClose_Verb:
1643 this->close();
1644 break;
1645 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001646 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001648 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 }
Mike Reedb6317422018-08-15 10:23:39 -04001650 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651}
1652
1653///////////////////////////////////////////////////////////////////////////////
1654
reed@google.com277c3f82013-05-31 15:17:50 +00001655static int pts_in_verb(unsigned verb) {
1656 static const uint8_t gPtsInVerb[] = {
1657 1, // kMove
1658 1, // kLine
1659 2, // kQuad
1660 2, // kConic
1661 3, // kCubic
1662 0, // kClose
1663 0 // kDone
1664 };
1665
1666 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1667 return gPtsInVerb[verb];
1668}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001669
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670// ignore the last point of the 1st contour
Mike Reedb6317422018-08-15 10:23:39 -04001671SkPath& SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001672 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1673 if (!verbs) { // empty path returns nullptr
Mike Reedb6317422018-08-15 10:23:39 -04001674 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 }
caryclark51c56782016-11-07 05:09:28 -08001676 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1677 SkASSERT(verbsEnd[0] == kMove_Verb);
1678 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1679 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001680
caryclark51c56782016-11-07 05:09:28 -08001681 while (verbs < verbsEnd) {
1682 uint8_t v = *verbs++;
1683 pts -= pts_in_verb(v);
1684 switch (v) {
1685 case kMove_Verb:
1686 // if the path has multiple contours, stop after reversing the last
Mike Reedb6317422018-08-15 10:23:39 -04001687 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001689 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 break;
1691 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001692 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001693 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001694 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001695 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001696 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001698 this->cubicTo(pts[2], pts[1], pts[0]);
1699 break;
1700 case kClose_Verb:
1701 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 break;
1703 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001704 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 break;
1706 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 }
Mike Reedb6317422018-08-15 10:23:39 -04001708 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709}
1710
Robert Phillips8051d382018-09-13 08:22:15 -04001711SkPath& SkPath::reverseAddPath(const SkPath& srcPath) {
1712 // Detect if we're trying to add ourself
1713 const SkPath* src = &srcPath;
1714 SkTLazy<SkPath> tmp;
1715 if (this == src) {
1716 src = tmp.set(srcPath);
1717 }
reed@google.com63d73742012-01-10 15:33:12 +00001718
Robert Phillips8051d382018-09-13 08:22:15 -04001719 SkPathRef::Editor ed(&fPathRef, src->fPathRef->countPoints(), src->fPathRef->countVerbs());
1720
1721 const SkPoint* pts = src->fPathRef->pointsEnd();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 // we will iterator through src's verbs backwards
Robert Phillips8051d382018-09-13 08:22:15 -04001723 const uint8_t* verbs = src->fPathRef->verbsMemBegin(); // points at the last verb
1724 const uint8_t* verbsEnd = src->fPathRef->verbs(); // points just past the first verb
1725 const SkScalar* conicWeights = src->fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001726
1727 bool needMove = true;
1728 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001729 while (verbs < verbsEnd) {
1730 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001731 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001732
1733 if (needMove) {
1734 --pts;
1735 this->moveTo(pts->fX, pts->fY);
1736 needMove = false;
1737 }
1738 pts -= n;
1739 switch (v) {
1740 case kMove_Verb:
1741 if (needClose) {
1742 this->close();
1743 needClose = false;
1744 }
1745 needMove = true;
1746 pts += 1; // so we see the point in "if (needMove)" above
1747 break;
1748 case kLine_Verb:
1749 this->lineTo(pts[0]);
1750 break;
1751 case kQuad_Verb:
1752 this->quadTo(pts[1], pts[0]);
1753 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001754 case kConic_Verb:
1755 this->conicTo(pts[1], pts[0], *--conicWeights);
1756 break;
reed@google.com63d73742012-01-10 15:33:12 +00001757 case kCubic_Verb:
1758 this->cubicTo(pts[2], pts[1], pts[0]);
1759 break;
1760 case kClose_Verb:
1761 needClose = true;
1762 break;
1763 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001764 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001765 }
1766 }
Mike Reedb6317422018-08-15 10:23:39 -04001767 return *this;
reed@google.com63d73742012-01-10 15:33:12 +00001768}
1769
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770///////////////////////////////////////////////////////////////////////////////
1771
1772void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1773 SkMatrix matrix;
1774
1775 matrix.setTranslate(dx, dy);
1776 this->transform(matrix, dst);
1777}
1778
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1780 int level = 2) {
1781 if (--level >= 0) {
1782 SkPoint tmp[7];
1783
1784 SkChopCubicAtHalf(pts, tmp);
1785 subdivide_cubic_to(path, &tmp[0], level);
1786 subdivide_cubic_to(path, &tmp[3], level);
1787 } else {
1788 path->cubicTo(pts[1], pts[2], pts[3]);
1789 }
1790}
1791
1792void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1793 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001794 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 dst = (SkPath*)this;
1796 }
1797
tomhudson@google.com8d430182011-06-06 19:11:19 +00001798 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 SkPath tmp;
1800 tmp.fFillType = fFillType;
1801
1802 SkPath::Iter iter(*this, false);
1803 SkPoint pts[4];
1804 SkPath::Verb verb;
1805
reed@google.com4a3b7142012-05-16 17:16:46 +00001806 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 switch (verb) {
1808 case kMove_Verb:
1809 tmp.moveTo(pts[0]);
1810 break;
1811 case kLine_Verb:
1812 tmp.lineTo(pts[1]);
1813 break;
1814 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001815 // promote the quad to a conic
1816 tmp.conicTo(pts[1], pts[2],
1817 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001819 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001820 tmp.conicTo(pts[1], pts[2],
1821 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001822 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 case kCubic_Verb:
1824 subdivide_cubic_to(&tmp, pts);
1825 break;
1826 case kClose_Verb:
1827 tmp.close();
1828 break;
1829 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001830 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831 break;
1832 }
1833 }
1834
1835 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 SkPathRef::Editor ed(&dst->fPathRef);
1837 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001838 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001840 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1841
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001843 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001844 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001845 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001847
reed026beb52015-06-10 14:23:15 -07001848 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1849 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001850 } else {
1851 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001852 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1853 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001854 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001855 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1856 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001857 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001858 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001859 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001860 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001861 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001862 }
1863 }
1864
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865 SkDEBUGCODE(dst->validate();)
1866 }
1867}
1868
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869///////////////////////////////////////////////////////////////////////////////
1870///////////////////////////////////////////////////////////////////////////////
1871
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872SkPath::Iter::Iter() {
1873#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001874 fPts = nullptr;
1875 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001876 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001878 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879#endif
1880 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001881 fVerbs = nullptr;
1882 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 fNeedClose = false;
1884}
1885
1886SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1887 this->setPath(path, forceClose);
1888}
1889
1890void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 fPts = path.fPathRef->points();
1892 fVerbs = path.fPathRef->verbs();
1893 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001894 fConicWeights = path.fPathRef->conicWeights();
1895 if (fConicWeights) {
1896 fConicWeights -= 1; // begin one behind
1897 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001899 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 fForceClose = SkToU8(forceClose);
1901 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001902 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903}
1904
1905bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001906 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 return false;
1908 }
1909 if (fForceClose) {
1910 return true;
1911 }
1912
1913 const uint8_t* verbs = fVerbs;
1914 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001915
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 if (kMove_Verb == *(verbs - 1)) {
1917 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 }
1919
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001920 while (verbs > stop) {
1921 // verbs points one beyond the current verb, decrement first.
1922 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001923 if (kMove_Verb == v) {
1924 break;
1925 }
1926 if (kClose_Verb == v) {
1927 return true;
1928 }
1929 }
1930 return false;
1931}
1932
1933SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001934 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001935 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001936 // A special case: if both points are NaN, SkPoint::operation== returns
1937 // false, but the iterator expects that they are treated as the same.
1938 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001939 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1940 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001941 return kClose_Verb;
1942 }
1943
reed@google.com9e25dbf2012-05-15 17:05:38 +00001944 pts[0] = fLastPt;
1945 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 fLastPt = fMoveTo;
1947 fCloseLine = true;
1948 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001949 } else {
1950 pts[0] = fMoveTo;
1951 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953}
1954
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 if (fSegmentState == kAfterMove_SegmentState) {
1957 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001959 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001961
1962 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1963 // Set the first return pt to the last pt of the previous primitive.
1964 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965}
1966
caryclarke8c56662015-07-14 11:19:26 -07001967void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968 // We need to step over anything that will not move the current draw point
1969 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001970 const uint8_t* lastMoveVerb = nullptr;
1971 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001972 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001973 SkPoint lastPt = fLastPt;
1974 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001975 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 switch (verb) {
1977 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 // Keep a record of this most recent move
1979 lastMoveVerb = fVerbs;
1980 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001981 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001982 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001983 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001984 fPts++;
1985 break;
1986
1987 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001988 // A close when we are in a segment is always valid except when it
1989 // follows a move which follows a segment.
1990 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991 return;
1992 }
1993 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001994 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001995 break;
1996
1997 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001998 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001999 if (lastMoveVerb) {
2000 fVerbs = lastMoveVerb;
2001 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002002 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003 return;
2004 }
2005 return;
2006 }
2007 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002008 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002009 fPts++;
2010 break;
2011
reed@google.com277c3f82013-05-31 15:17:50 +00002012 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002013 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002014 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002015 if (lastMoveVerb) {
2016 fVerbs = lastMoveVerb;
2017 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002018 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002019 return;
2020 }
2021 return;
2022 }
2023 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002024 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002025 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00002026 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002027 break;
2028
2029 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002030 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002031 if (lastMoveVerb) {
2032 fVerbs = lastMoveVerb;
2033 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002034 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002035 return;
2036 }
2037 return;
2038 }
2039 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002040 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002041 fPts += 3;
2042 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002043
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002044 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002045 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002046 }
2047 }
2048}
2049
reed@google.com4a3b7142012-05-16 17:16:46 +00002050SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002051 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002052
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002054 // Close the curve if requested and if there is some curve to close
2055 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002056 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 return kLine_Verb;
2058 }
2059 fNeedClose = false;
2060 return kClose_Verb;
2061 }
2062 return kDone_Verb;
2063 }
2064
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002065 // fVerbs is one beyond the current verb, decrement first
2066 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002067 const SkPoint* SK_RESTRICT srcPts = fPts;
2068 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069
2070 switch (verb) {
2071 case kMove_Verb:
2072 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002073 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074 verb = this->autoClose(pts);
2075 if (verb == kClose_Verb) {
2076 fNeedClose = false;
2077 }
2078 return (Verb)verb;
2079 }
2080 if (fVerbs == fVerbStop) { // might be a trailing moveto
2081 return kDone_Verb;
2082 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002083 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002084 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002085 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002086 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002087 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088 fNeedClose = fForceClose;
2089 break;
2090 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002091 pts[0] = this->cons_moveTo();
2092 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002093 fLastPt = srcPts[0];
2094 fCloseLine = false;
2095 srcPts += 1;
2096 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002097 case kConic_Verb:
2098 fConicWeights += 1;
2099 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002101 pts[0] = this->cons_moveTo();
2102 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 fLastPt = srcPts[1];
2104 srcPts += 2;
2105 break;
2106 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002107 pts[0] = this->cons_moveTo();
2108 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 fLastPt = srcPts[2];
2110 srcPts += 3;
2111 break;
2112 case kClose_Verb:
2113 verb = this->autoClose(pts);
2114 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002115 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 } else {
2117 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002118 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002120 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121 break;
2122 }
2123 fPts = srcPts;
2124 return (Verb)verb;
2125}
2126
2127///////////////////////////////////////////////////////////////////////////////
2128
Ben Wagner4d1955c2017-03-10 13:08:15 -05002129#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002130#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002131#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002132
reed@google.com51bbe752013-01-17 22:07:50 +00002133static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002134 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002135 str->append(label);
2136 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002137
reed@google.com51bbe752013-01-17 22:07:50 +00002138 const SkScalar* values = &pts[0].fX;
2139 count *= 2;
2140
2141 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002142 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002143 if (i < count - 1) {
2144 str->append(", ");
2145 }
2146 }
Mike Reed405b9d22018-01-09 15:11:08 -05002147 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002148 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002149 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002150 }
caryclark08fa28c2014-10-23 13:08:56 -07002151 str->append(");");
reede05fed02014-12-15 07:59:53 -08002152 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002153 str->append(" // ");
2154 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002155 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002156 if (i < count - 1) {
2157 str->append(", ");
2158 }
2159 }
2160 if (conicWeight >= 0) {
2161 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002162 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002163 }
2164 }
2165 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002166}
2167
caryclarke9562592014-09-15 09:26:09 -07002168void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002169 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170 Iter iter(*this, forceClose);
2171 SkPoint pts[4];
2172 Verb verb;
2173
reed@google.com51bbe752013-01-17 22:07:50 +00002174 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002175 char const * const gFillTypeStrs[] = {
2176 "Winding",
2177 "EvenOdd",
2178 "InverseWinding",
2179 "InverseEvenOdd",
2180 };
2181 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2182 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002183 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002184 switch (verb) {
2185 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002186 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187 break;
2188 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002189 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 break;
2191 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002192 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002194 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002195 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002196 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002197 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002198 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199 break;
2200 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002201 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002202 break;
2203 default:
2204 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2205 verb = kDone_Verb; // stop the loop
2206 break;
2207 }
caryclark1049f122015-04-20 08:31:59 -07002208 if (!wStream && builder.size()) {
2209 SkDebugf("%s", builder.c_str());
2210 builder.reset();
2211 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212 }
caryclark66a5d8b2014-06-24 08:30:15 -07002213 if (wStream) {
2214 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002215 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002216}
2217
reed@android.come522ca52009-11-23 20:10:41 +00002218void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002219 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002220}
2221
2222void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002223 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002224}
2225
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002226
Cary Clark0461e9f2017-08-25 15:13:38 -04002227bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002228 if ((fFillType & ~3) != 0) {
2229 return false;
2230 }
reed@google.comabf15c12011-01-18 20:35:51 +00002231
djsollen@google.com077348c2012-10-22 20:23:32 +00002232#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002233 if (!fBoundsIsDirty) {
2234 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002235
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002236 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002237 if (SkToBool(fIsFinite) != isFinite) {
2238 return false;
2239 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002240
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002241 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002242 // if we're empty, fBounds may be empty but translated, so we can't
2243 // necessarily compare to bounds directly
2244 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2245 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002246 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2247 return false;
2248 }
reed@android.come522ca52009-11-23 20:10:41 +00002249 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002250 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002251 if (!fBounds.isEmpty()) {
2252 return false;
2253 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002254 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002255 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002256 if (!fBounds.contains(bounds)) {
2257 return false;
2258 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002259 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002260 }
reed@android.come522ca52009-11-23 20:10:41 +00002261 }
2262 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002263#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002264 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002265}
reed@android.come522ca52009-11-23 20:10:41 +00002266
reed@google.com04863fa2011-05-15 04:08:24 +00002267///////////////////////////////////////////////////////////////////////////////
2268
reed@google.com0b7b9822011-05-16 12:29:27 +00002269static int sign(SkScalar x) { return x < 0; }
2270#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002271
robertphillipsc506e302014-09-16 09:43:31 -07002272enum DirChange {
2273 kLeft_DirChange,
2274 kRight_DirChange,
2275 kStraight_DirChange,
2276 kBackwards_DirChange,
2277
2278 kInvalid_DirChange
2279};
2280
2281
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002282static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002283 // The error epsilon was empirically derived; worse case round rects
2284 // with a mid point outset by 2x float epsilon in tests had an error
2285 // of 12.
2286 const int epsilon = 16;
2287 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2288 return false;
2289 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002290 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002291 int aBits = SkFloatAs2sCompliment(compA);
2292 int bBits = SkFloatAs2sCompliment(compB);
2293 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002294}
2295
caryclarkb4216502015-03-02 13:02:34 -08002296static bool approximately_zero_when_compared_to(double x, double y) {
2297 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002298}
2299
caryclarkb4216502015-03-02 13:02:34 -08002300
reed@google.com04863fa2011-05-15 04:08:24 +00002301// only valid for a single contour
2302struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002303 Convexicator()
2304 : fPtCount(0)
2305 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002306 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002307 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002308 , fIsCurve(false)
2309 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002310 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002311 // warnings
djsollen2f124632016-04-29 13:53:05 -07002312 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002313 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002314 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002315 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002316 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002317
2318 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002319 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002320 }
2321
2322 SkPath::Convexity getConvexity() const { return fConvexity; }
2323
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002324 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002325 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002326
reed@google.com04863fa2011-05-15 04:08:24 +00002327 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002328 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002329 return;
2330 }
2331
2332 if (0 == fPtCount) {
2333 fCurrPt = pt;
2334 ++fPtCount;
2335 } else {
2336 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002337 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002338 if (!SkScalarIsFinite(lengthSqd)) {
2339 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002340 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002341 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002342 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002343 fCurrPt = pt;
2344 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002345 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002346 } else {
2347 SkASSERT(fPtCount > 2);
2348 this->addVec(vec);
2349 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002350
reed@google.com85b6e392011-05-15 20:25:17 +00002351 int sx = sign(vec.fX);
2352 int sy = sign(vec.fY);
2353 fDx += (sx != fSx);
2354 fDy += (sy != fSy);
2355 fSx = sx;
2356 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002357
reed@google.com85b6e392011-05-15 20:25:17 +00002358 if (fDx > 3 || fDy > 3) {
2359 fConvexity = SkPath::kConcave_Convexity;
2360 }
reed@google.com04863fa2011-05-15 04:08:24 +00002361 }
2362 }
2363 }
2364
2365 void close() {
2366 if (fPtCount > 2) {
2367 this->addVec(fFirstVec);
2368 }
2369 }
2370
caryclarkb4216502015-03-02 13:02:34 -08002371 DirChange directionChange(const SkVector& curVec) {
2372 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2373
2374 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2375 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2376 largest = SkTMax(largest, -smallest);
2377
2378 if (!almost_equal(largest, largest + cross)) {
2379 int sign = SkScalarSignAsInt(cross);
2380 if (sign) {
2381 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2382 }
2383 }
2384
2385 if (cross) {
2386 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2387 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2388 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2389 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2390 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2391 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2392 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2393 if (sign) {
2394 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2395 }
2396 }
2397 }
2398
Cary Clarkdf429f32017-11-08 11:44:31 -05002399 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2400 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2401 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2402 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002403 fLastVec.dot(curVec) < 0.0f) {
2404 return kBackwards_DirChange;
2405 }
2406
2407 return kStraight_DirChange;
2408 }
2409
Cary Clarkc8323aa2017-08-25 08:04:43 -04002410 bool hasBackwards() const {
2411 return fBackwards;
2412 }
caryclarkb4216502015-03-02 13:02:34 -08002413
caryclarkd3d1a982014-12-08 04:57:38 -08002414 bool isFinite() const {
2415 return fIsFinite;
2416 }
2417
caryclark5ccef572015-03-02 10:07:56 -08002418 void setCurve(bool isCurve) {
2419 fIsCurve = isCurve;
2420 }
2421
reed@google.com04863fa2011-05-15 04:08:24 +00002422private:
2423 void addVec(const SkVector& vec) {
2424 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002425 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002426 switch (dir) {
2427 case kLeft_DirChange: // fall through
2428 case kRight_DirChange:
2429 if (kInvalid_DirChange == fExpectedDir) {
2430 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002431 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2432 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002433 } else if (dir != fExpectedDir) {
2434 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002435 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002436 }
2437 fLastVec = vec;
2438 break;
2439 case kStraight_DirChange:
2440 break;
2441 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002442 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002443 // If any of the subsequent dir is non-backward, it'll be concave.
2444 // Otherwise, it's still convex.
2445 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002446 }
robertphillipsc506e302014-09-16 09:43:31 -07002447 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002448 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002449 break;
2450 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002451 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002452 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002453 }
2454 }
2455
caryclarkb4216502015-03-02 13:02:34 -08002456 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002457 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002458 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002459 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2460 // value with the current vec is deemed to be of a significant value.
2461 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002462 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002463 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002464 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002465 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002466 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002467 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002468 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002469 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002470};
2471
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002472SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002473 // Sometimes we think we need to calculate convexity but another thread already did.
2474 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2475 return c;
2476 }
2477
reed@google.com04863fa2011-05-15 04:08:24 +00002478 SkPoint pts[4];
2479 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002480 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002481
2482 int contourCount = 0;
2483 int count;
2484 Convexicator state;
2485
caryclarkd3d1a982014-12-08 04:57:38 -08002486 if (!isFinite()) {
2487 return kUnknown_Convexity;
2488 }
Brian Osman205a1262017-09-18 13:13:48 +00002489 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002490 switch (verb) {
2491 case kMove_Verb:
2492 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002493 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002494 return kConcave_Convexity;
2495 }
2496 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002497 // fall through
2498 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002499 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002500 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002501 break;
caryclark5ccef572015-03-02 10:07:56 -08002502 case kQuad_Verb:
2503 // fall through
2504 case kConic_Verb:
2505 // fall through
2506 case kCubic_Verb:
2507 count = 2 + (kCubic_Verb == verb);
2508 // As an additional enhancement, this could set curve true only
2509 // if the curve is nonlinear
2510 state.setCurve(true);
2511 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002512 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002513 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002514 state.close();
2515 count = 0;
2516 break;
2517 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002518 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002519 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002520 return kConcave_Convexity;
2521 }
2522
2523 for (int i = 1; i <= count; i++) {
2524 state.addPt(pts[i]);
2525 }
2526 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002527 if (!state.isFinite()) {
2528 return kUnknown_Convexity;
2529 }
reed@google.com04863fa2011-05-15 04:08:24 +00002530 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002531 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002532 return kConcave_Convexity;
2533 }
2534 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002535 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002536 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002537 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2538 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2539 fConvexity = Convexity::kConcave_Convexity;
2540 } else {
2541 fFirstDirection = state.getFirstDirection();
2542 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002543 }
2544 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002545}
reed@google.com69a99432012-01-10 18:00:10 +00002546
2547///////////////////////////////////////////////////////////////////////////////
2548
2549class ContourIter {
2550public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002551 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002552
2553 bool done() const { return fDone; }
2554 // if !done() then these may be called
2555 int count() const { return fCurrPtCount; }
2556 const SkPoint* pts() const { return fCurrPt; }
2557 void next();
2558
2559private:
2560 int fCurrPtCount;
2561 const SkPoint* fCurrPt;
2562 const uint8_t* fCurrVerb;
2563 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002564 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002565 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002566 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002567};
2568
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002569ContourIter::ContourIter(const SkPathRef& pathRef) {
2570 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002571 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002572 fCurrPt = pathRef.points();
2573 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002574 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002575 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002576 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002577 this->next();
2578}
2579
2580void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002581 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002582 fDone = true;
2583 }
2584 if (fDone) {
2585 return;
2586 }
2587
2588 // skip pts of prev contour
2589 fCurrPt += fCurrPtCount;
2590
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002591 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002592 int ptCount = 1; // moveTo
2593 const uint8_t* verbs = fCurrVerb;
2594
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002595 for (--verbs; verbs > fStopVerbs; --verbs) {
2596 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002597 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002598 goto CONTOUR_END;
2599 case SkPath::kLine_Verb:
2600 ptCount += 1;
2601 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002602 case SkPath::kConic_Verb:
2603 fCurrConicWeight += 1;
2604 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002605 case SkPath::kQuad_Verb:
2606 ptCount += 2;
2607 break;
2608 case SkPath::kCubic_Verb:
2609 ptCount += 3;
2610 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002611 case SkPath::kClose_Verb:
2612 break;
2613 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002614 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002615 break;
2616 }
2617 }
2618CONTOUR_END:
2619 fCurrPtCount = ptCount;
2620 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002621 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002622}
2623
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002624// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002625static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002626 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2627 // We may get 0 when the above subtracts underflow. We expect this to be
2628 // very rare and lazily promote to double.
2629 if (0 == cross) {
2630 double p0x = SkScalarToDouble(p0.fX);
2631 double p0y = SkScalarToDouble(p0.fY);
2632
2633 double p1x = SkScalarToDouble(p1.fX);
2634 double p1y = SkScalarToDouble(p1.fY);
2635
2636 double p2x = SkScalarToDouble(p2.fX);
2637 double p2y = SkScalarToDouble(p2.fY);
2638
2639 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2640 (p1y - p0y) * (p2x - p0x));
2641
2642 }
2643 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002644}
2645
reed@google.comc1ea60a2012-01-31 15:15:36 +00002646// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002647static int find_max_y(const SkPoint pts[], int count) {
2648 SkASSERT(count > 0);
2649 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002650 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002651 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002652 SkScalar y = pts[i].fY;
2653 if (y > max) {
2654 max = y;
2655 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002656 }
2657 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002658 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002659}
2660
reed@google.comcabaf1d2012-01-11 21:03:05 +00002661static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2662 int i = index;
2663 for (;;) {
2664 i = (i + inc) % n;
2665 if (i == index) { // we wrapped around, so abort
2666 break;
2667 }
2668 if (pts[index] != pts[i]) { // found a different point, success!
2669 break;
2670 }
2671 }
2672 return i;
2673}
2674
reed@google.comc1ea60a2012-01-31 15:15:36 +00002675/**
2676 * Starting at index, and moving forward (incrementing), find the xmin and
2677 * xmax of the contiguous points that have the same Y.
2678 */
2679static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2680 int* maxIndexPtr) {
2681 const SkScalar y = pts[index].fY;
2682 SkScalar min = pts[index].fX;
2683 SkScalar max = min;
2684 int minIndex = index;
2685 int maxIndex = index;
2686 for (int i = index + 1; i < n; ++i) {
2687 if (pts[i].fY != y) {
2688 break;
2689 }
2690 SkScalar x = pts[i].fX;
2691 if (x < min) {
2692 min = x;
2693 minIndex = i;
2694 } else if (x > max) {
2695 max = x;
2696 maxIndex = i;
2697 }
2698 }
2699 *maxIndexPtr = maxIndex;
2700 return minIndex;
2701}
2702
reed026beb52015-06-10 14:23:15 -07002703static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2704 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002705}
2706
reed@google.comac8543f2012-01-30 20:51:25 +00002707/*
2708 * We loop through all contours, and keep the computed cross-product of the
2709 * contour that contained the global y-max. If we just look at the first
2710 * contour, we may find one that is wound the opposite way (correctly) since
2711 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2712 * that is outer most (or at least has the global y-max) before we can consider
2713 * its cross product.
2714 */
reed026beb52015-06-10 14:23:15 -07002715bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002716 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2717 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002718 return true;
2719 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002720
2721 // don't want to pay the cost for computing this if it
2722 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002723 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2724 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002725 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002726 return false;
2727 }
reed@google.com69a99432012-01-10 18:00:10 +00002728
reed026beb52015-06-10 14:23:15 -07002729 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002730
reed@google.comac8543f2012-01-30 20:51:25 +00002731 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002732 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002733 SkScalar ymaxCross = 0;
2734
reed@google.com69a99432012-01-10 18:00:10 +00002735 for (; !iter.done(); iter.next()) {
2736 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002737 if (n < 3) {
2738 continue;
2739 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002740
reed@google.comcabaf1d2012-01-11 21:03:05 +00002741 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002742 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002743 int index = find_max_y(pts, n);
2744 if (pts[index].fY < ymax) {
2745 continue;
2746 }
2747
2748 // If there is more than 1 distinct point at the y-max, we take the
2749 // x-min and x-max of them and just subtract to compute the dir.
2750 if (pts[(index + 1) % n].fY == pts[index].fY) {
2751 int maxIndex;
2752 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2753 if (minIndex == maxIndex) {
2754 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002755 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002756 SkASSERT(pts[minIndex].fY == pts[index].fY);
2757 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2758 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2759 // we just subtract the indices, and let that auto-convert to
2760 // SkScalar, since we just want - or + to signal the direction.
2761 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002762 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002763 TRY_CROSSPROD:
2764 // Find a next and prev index to use for the cross-product test,
2765 // but we try to find pts that form non-zero vectors from pts[index]
2766 //
2767 // Its possible that we can't find two non-degenerate vectors, so
2768 // we have to guard our search (e.g. all the pts could be in the
2769 // same place).
2770
2771 // we pass n - 1 instead of -1 so we don't foul up % operator by
2772 // passing it a negative LH argument.
2773 int prev = find_diff_pt(pts, index, n, n - 1);
2774 if (prev == index) {
2775 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002776 continue;
2777 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002778 int next = find_diff_pt(pts, index, n, 1);
2779 SkASSERT(next != index);
2780 cross = cross_prod(pts[prev], pts[index], pts[next]);
2781 // if we get a zero and the points are horizontal, then we look at the spread in
2782 // x-direction. We really should continue to walk away from the degeneracy until
2783 // there is a divergence.
2784 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2785 // construct the subtract so we get the correct Direction below
2786 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002787 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002788 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002789
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002790 if (cross) {
2791 // record our best guess so far
2792 ymax = pts[index].fY;
2793 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002794 }
2795 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002796 if (ymaxCross) {
2797 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002798 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002799 return true;
2800 } else {
2801 return false;
2802 }
reed@google.comac8543f2012-01-30 20:51:25 +00002803}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804
2805///////////////////////////////////////////////////////////////////////////////
2806
caryclark9aacd902015-12-14 08:38:09 -08002807static bool between(SkScalar a, SkScalar b, SkScalar c) {
2808 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2809 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2810 return (a - b) * (c - b) <= 0;
2811}
2812
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2814 SkScalar t) {
2815 SkScalar A = c3 + 3*(c1 - c2) - c0;
2816 SkScalar B = 3*(c2 - c1 - c1 + c0);
2817 SkScalar C = 3*(c1 - c0);
2818 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002819 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820}
2821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822template <size_t N> static void find_minmax(const SkPoint pts[],
2823 SkScalar* minPtr, SkScalar* maxPtr) {
2824 SkScalar min, max;
2825 min = max = pts[0].fX;
2826 for (size_t i = 1; i < N; ++i) {
2827 min = SkMinScalar(min, pts[i].fX);
2828 max = SkMaxScalar(max, pts[i].fX);
2829 }
2830 *minPtr = min;
2831 *maxPtr = max;
2832}
2833
caryclark9cb5d752015-12-18 04:35:24 -08002834static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2835 if (start.fY == end.fY) {
2836 return between(start.fX, x, end.fX) && x != end.fX;
2837 } else {
2838 return x == start.fX && y == start.fY;
2839 }
2840}
2841
caryclark9aacd902015-12-14 08:38:09 -08002842static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002843 SkScalar y0 = pts[0].fY;
2844 SkScalar y3 = pts[3].fY;
2845
2846 int dir = 1;
2847 if (y0 > y3) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002848 using std::swap;
2849 swap(y0, y3);
caryclark9cb5d752015-12-18 04:35:24 -08002850 dir = -1;
2851 }
2852 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 return 0;
2854 }
caryclark9cb5d752015-12-18 04:35:24 -08002855 if (checkOnCurve(x, y, pts[0], pts[3])) {
2856 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002857 return 0;
2858 }
caryclark9cb5d752015-12-18 04:35:24 -08002859 if (y == y3) {
2860 return 0;
2861 }
2862
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 // quickreject or quickaccept
2864 SkScalar min, max;
2865 find_minmax<4>(pts, &min, &max);
2866 if (x < min) {
2867 return 0;
2868 }
2869 if (x > max) {
2870 return dir;
2871 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002872
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002874 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002875 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002876 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002877 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002878 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002879 if (SkScalarNearlyEqual(xt, x)) {
2880 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2881 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002882 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002883 }
2884 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 return xt < x ? dir : 0;
2886}
2887
caryclark9aacd902015-12-14 08:38:09 -08002888static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889 SkPoint dst[10];
2890 int n = SkChopCubicAtYExtrema(pts, dst);
2891 int w = 0;
2892 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002893 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 }
2895 return w;
2896}
2897
caryclark9aacd902015-12-14 08:38:09 -08002898static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2899 SkASSERT(src);
2900 SkASSERT(t >= 0 && t <= 1);
2901 SkScalar src2w = src[2] * w;
2902 SkScalar C = src[0];
2903 SkScalar A = src[4] - 2 * src2w + C;
2904 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002905 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002906}
2907
2908
2909static double conic_eval_denominator(SkScalar w, SkScalar t) {
2910 SkScalar B = 2 * (w - 1);
2911 SkScalar C = 1;
2912 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002913 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002914}
2915
2916static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2917 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002918 SkScalar y0 = pts[0].fY;
2919 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002920
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002921 int dir = 1;
2922 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002923 using std::swap;
2924 swap(y0, y2);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002925 dir = -1;
2926 }
caryclark9aacd902015-12-14 08:38:09 -08002927 if (y < y0 || y > y2) {
2928 return 0;
2929 }
caryclark9cb5d752015-12-18 04:35:24 -08002930 if (checkOnCurve(x, y, pts[0], pts[2])) {
2931 *onCurveCount += 1;
2932 return 0;
2933 }
caryclark9aacd902015-12-14 08:38:09 -08002934 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002935 return 0;
2936 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002937
caryclark9aacd902015-12-14 08:38:09 -08002938 SkScalar roots[2];
2939 SkScalar A = pts[2].fY;
2940 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2941 SkScalar C = pts[0].fY;
2942 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2943 B -= C; // B = b*w - w * yCept + yCept - a
2944 C -= y;
2945 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2946 SkASSERT(n <= 1);
2947 SkScalar xt;
2948 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002949 // zero roots are returned only when y0 == y
2950 // Need [0] if dir == 1
2951 // and [2] if dir == -1
2952 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002953 } else {
2954 SkScalar t = roots[0];
2955 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2956 }
2957 if (SkScalarNearlyEqual(xt, x)) {
2958 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2959 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002960 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002961 }
2962 }
2963 return xt < x ? dir : 0;
2964}
2965
2966static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2967 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2968 if (y0 == y1) {
2969 return true;
2970 }
2971 if (y0 < y1) {
2972 return y1 <= y2;
2973 } else {
2974 return y1 >= y2;
2975 }
2976}
2977
2978static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2979 int* onCurveCount) {
2980 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002981 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002982 // If the data points are very large, the conic may not be monotonic but may also
2983 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002984 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2985 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2986 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002987 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2988 }
2989 return w;
2990}
2991
2992static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2993 SkScalar y0 = pts[0].fY;
2994 SkScalar y2 = pts[2].fY;
2995
2996 int dir = 1;
2997 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002998 using std::swap;
2999 swap(y0, y2);
caryclark9aacd902015-12-14 08:38:09 -08003000 dir = -1;
3001 }
3002 if (y < y0 || y > y2) {
3003 return 0;
3004 }
caryclark9cb5d752015-12-18 04:35:24 -08003005 if (checkOnCurve(x, y, pts[0], pts[2])) {
3006 *onCurveCount += 1;
3007 return 0;
3008 }
caryclark9aacd902015-12-14 08:38:09 -08003009 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003010 return 0;
3011 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 // bounds check on X (not required. is it faster?)
3013#if 0
3014 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3015 return 0;
3016 }
3017#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003018
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003019 SkScalar roots[2];
3020 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3021 2 * (pts[1].fY - pts[0].fY),
3022 pts[0].fY - y,
3023 roots);
3024 SkASSERT(n <= 1);
3025 SkScalar xt;
3026 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003027 // zero roots are returned only when y0 == y
3028 // Need [0] if dir == 1
3029 // and [2] if dir == -1
3030 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 } else {
3032 SkScalar t = roots[0];
3033 SkScalar C = pts[0].fX;
3034 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3035 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003036 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003037 }
caryclark9aacd902015-12-14 08:38:09 -08003038 if (SkScalarNearlyEqual(xt, x)) {
3039 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3040 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003041 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003042 }
3043 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003044 return xt < x ? dir : 0;
3045}
3046
caryclark9aacd902015-12-14 08:38:09 -08003047static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003048 SkPoint dst[5];
3049 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003050
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003051 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3052 n = SkChopQuadAtYExtrema(pts, dst);
3053 pts = dst;
3054 }
caryclark9aacd902015-12-14 08:38:09 -08003055 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003056 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003057 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003058 }
3059 return w;
3060}
3061
caryclark9aacd902015-12-14 08:38:09 -08003062static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003063 SkScalar x0 = pts[0].fX;
3064 SkScalar y0 = pts[0].fY;
3065 SkScalar x1 = pts[1].fX;
3066 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003067
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003068 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003069
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003070 int dir = 1;
3071 if (y0 > y1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04003072 using std::swap;
3073 swap(y0, y1);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003074 dir = -1;
3075 }
caryclark9aacd902015-12-14 08:38:09 -08003076 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003077 return 0;
3078 }
caryclark9cb5d752015-12-18 04:35:24 -08003079 if (checkOnCurve(x, y, pts[0], pts[1])) {
3080 *onCurveCount += 1;
3081 return 0;
3082 }
3083 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003084 return 0;
3085 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003086 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003087
caryclark9aacd902015-12-14 08:38:09 -08003088 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003089 // zero cross means the point is on the line, and since the case where
3090 // y of the query point is at the end point is handled above, we can be
3091 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003092 if (x != x1 || y != pts[1].fY) {
3093 *onCurveCount += 1;
3094 }
caryclark9aacd902015-12-14 08:38:09 -08003095 dir = 0;
3096 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003097 dir = 0;
3098 }
3099 return dir;
3100}
3101
caryclark9aacd902015-12-14 08:38:09 -08003102static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3103 SkTDArray<SkVector>* tangents) {
3104 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3105 && !between(pts[2].fY, y, pts[3].fY)) {
3106 return;
3107 }
3108 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3109 && !between(pts[2].fX, x, pts[3].fX)) {
3110 return;
3111 }
3112 SkPoint dst[10];
3113 int n = SkChopCubicAtYExtrema(pts, dst);
3114 for (int i = 0; i <= n; ++i) {
3115 SkPoint* c = &dst[i * 3];
3116 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003117 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003118 continue;
mbarbella276e6332016-05-31 14:44:01 -07003119 }
caryclark9aacd902015-12-14 08:38:09 -08003120 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3121 if (!SkScalarNearlyEqual(x, xt)) {
3122 continue;
3123 }
3124 SkVector tangent;
3125 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
Mike Reed5edcd312018-08-08 11:23:41 -04003126 tangents->push_back(tangent);
caryclark9aacd902015-12-14 08:38:09 -08003127 }
3128}
3129
3130static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3131 SkTDArray<SkVector>* tangents) {
3132 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3133 return;
3134 }
3135 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3136 return;
3137 }
3138 SkScalar roots[2];
3139 SkScalar A = pts[2].fY;
3140 SkScalar B = pts[1].fY * w - y * w + y;
3141 SkScalar C = pts[0].fY;
3142 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3143 B -= C; // B = b*w - w * yCept + yCept - a
3144 C -= y;
3145 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3146 for (int index = 0; index < n; ++index) {
3147 SkScalar t = roots[index];
3148 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3149 if (!SkScalarNearlyEqual(x, xt)) {
3150 continue;
3151 }
3152 SkConic conic(pts, w);
Mike Reed5edcd312018-08-08 11:23:41 -04003153 tangents->push_back(conic.evalTangentAt(t));
caryclark9aacd902015-12-14 08:38:09 -08003154 }
3155}
3156
3157static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3158 SkTDArray<SkVector>* tangents) {
3159 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3160 return;
3161 }
3162 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3163 return;
3164 }
3165 SkScalar roots[2];
3166 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3167 2 * (pts[1].fY - pts[0].fY),
3168 pts[0].fY - y,
3169 roots);
3170 for (int index = 0; index < n; ++index) {
3171 SkScalar t = roots[index];
3172 SkScalar C = pts[0].fX;
3173 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3174 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003175 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003176 if (!SkScalarNearlyEqual(x, xt)) {
3177 continue;
3178 }
Mike Reed5edcd312018-08-08 11:23:41 -04003179 tangents->push_back(SkEvalQuadTangentAt(pts, t));
caryclark9aacd902015-12-14 08:38:09 -08003180 }
3181}
3182
3183static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3184 SkTDArray<SkVector>* tangents) {
3185 SkScalar y0 = pts[0].fY;
3186 SkScalar y1 = pts[1].fY;
3187 if (!between(y0, y, y1)) {
3188 return;
3189 }
3190 SkScalar x0 = pts[0].fX;
3191 SkScalar x1 = pts[1].fX;
3192 if (!between(x0, x, x1)) {
3193 return;
3194 }
3195 SkScalar dx = x1 - x0;
3196 SkScalar dy = y1 - y0;
3197 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3198 return;
3199 }
3200 SkVector v;
3201 v.set(dx, dy);
Mike Reed5edcd312018-08-08 11:23:41 -04003202 tangents->push_back(v);
caryclark9aacd902015-12-14 08:38:09 -08003203}
3204
reed@google.com4db592c2013-10-30 17:39:43 +00003205static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3206 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3207}
3208
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003209bool SkPath::contains(SkScalar x, SkScalar y) const {
3210 bool isInverse = this->isInverseFillType();
3211 if (this->isEmpty()) {
3212 return isInverse;
3213 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003214
reed@google.com4db592c2013-10-30 17:39:43 +00003215 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003216 return isInverse;
3217 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003218
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003219 SkPath::Iter iter(*this, true);
3220 bool done = false;
3221 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003222 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003223 do {
3224 SkPoint pts[4];
3225 switch (iter.next(pts, false)) {
3226 case SkPath::kMove_Verb:
3227 case SkPath::kClose_Verb:
3228 break;
3229 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003230 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003231 break;
3232 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003233 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003234 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003235 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003236 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003237 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003238 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003239 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003240 break;
3241 case SkPath::kDone_Verb:
3242 done = true;
3243 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003244 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003245 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003246 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3247 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3248 if (evenOddFill) {
3249 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003250 }
caryclark9aacd902015-12-14 08:38:09 -08003251 if (w) {
3252 return !isInverse;
3253 }
3254 if (onCurveCount <= 1) {
3255 return SkToBool(onCurveCount) ^ isInverse;
3256 }
3257 if ((onCurveCount & 1) || evenOddFill) {
3258 return SkToBool(onCurveCount & 1) ^ isInverse;
3259 }
halcanary9d524f22016-03-29 09:03:52 -07003260 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003261 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3262 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003263 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003264 SkTDArray<SkVector> tangents;
3265 do {
3266 SkPoint pts[4];
3267 int oldCount = tangents.count();
3268 switch (iter.next(pts, false)) {
3269 case SkPath::kMove_Verb:
3270 case SkPath::kClose_Verb:
3271 break;
3272 case SkPath::kLine_Verb:
3273 tangent_line(pts, x, y, &tangents);
3274 break;
3275 case SkPath::kQuad_Verb:
3276 tangent_quad(pts, x, y, &tangents);
3277 break;
3278 case SkPath::kConic_Verb:
3279 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3280 break;
3281 case SkPath::kCubic_Verb:
3282 tangent_cubic(pts, x, y, &tangents);
3283 break;
3284 case SkPath::kDone_Verb:
3285 done = true;
3286 break;
3287 }
3288 if (tangents.count() > oldCount) {
3289 int last = tangents.count() - 1;
3290 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003291 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003292 tangents.remove(last);
3293 } else {
3294 for (int index = 0; index < last; ++index) {
3295 const SkVector& test = tangents[index];
3296 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003297 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3298 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003299 tangents.remove(last);
3300 tangents.removeShuffle(index);
3301 break;
3302 }
3303 }
3304 }
3305 }
3306 } while (!done);
3307 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003308}
fmalitaaa0df4e2015-12-01 09:13:23 -08003309
3310int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3311 SkScalar w, SkPoint pts[], int pow2) {
3312 const SkConic conic(p0, p1, p2, w);
3313 return conic.chopIntoQuadsPOW2(pts, pow2);
3314}
bsalomonedc743a2016-06-01 09:42:31 -07003315
3316bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3317 unsigned* start) {
3318 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3319 return false;
3320 }
3321 SkPath::RawIter iter(path);
3322 SkPoint verbPts[4];
3323 SkPath::Verb v;
3324 SkPoint rectPts[5];
3325 int rectPtCnt = 0;
3326 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3327 switch (v) {
3328 case SkPath::kMove_Verb:
3329 if (0 != rectPtCnt) {
3330 return false;
3331 }
3332 rectPts[0] = verbPts[0];
3333 ++rectPtCnt;
3334 break;
3335 case SkPath::kLine_Verb:
3336 if (5 == rectPtCnt) {
3337 return false;
3338 }
3339 rectPts[rectPtCnt] = verbPts[1];
3340 ++rectPtCnt;
3341 break;
3342 case SkPath::kClose_Verb:
3343 if (4 == rectPtCnt) {
3344 rectPts[4] = rectPts[0];
3345 rectPtCnt = 5;
3346 }
3347 break;
3348 default:
3349 return false;
3350 }
3351 }
3352 if (rectPtCnt < 5) {
3353 return false;
3354 }
3355 if (rectPts[0] != rectPts[4]) {
3356 return false;
3357 }
bsalomon057ae8a2016-07-24 05:37:26 -07003358 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3359 // and pts 1 and 2 the opposite vertical or horizontal edge).
3360 bool vec03IsVertical;
3361 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3362 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3363 // Make sure it has non-zero width and height
3364 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003365 return false;
3366 }
bsalomon057ae8a2016-07-24 05:37:26 -07003367 vec03IsVertical = true;
3368 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3369 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3370 // Make sure it has non-zero width and height
3371 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3372 return false;
3373 }
3374 vec03IsVertical = false;
3375 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003376 return false;
3377 }
bsalomon057ae8a2016-07-24 05:37:26 -07003378 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3379 // set if it is on the bottom edge.
3380 unsigned sortFlags =
3381 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3382 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3383 switch (sortFlags) {
3384 case 0b00:
3385 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3386 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3387 *start = 0;
3388 break;
3389 case 0b01:
3390 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3391 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3392 *start = 1;
3393 break;
3394 case 0b10:
3395 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3396 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3397 *start = 3;
3398 break;
3399 case 0b11:
3400 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3401 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3402 *start = 2;
3403 break;
bsalomonedc743a2016-06-01 09:42:31 -07003404 }
bsalomonedc743a2016-06-01 09:42:31 -07003405 return true;
3406}
bsalomon21af9ca2016-08-25 12:29:23 -07003407
Brian Salomone4949402018-04-26 15:22:04 -04003408bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3409 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3410 // This gets converted to an oval.
3411 return true;
3412 }
3413 if (useCenter) {
3414 // This is a pie wedge. It's convex if the angle is <= 180.
3415 return SkScalarAbs(sweepAngle) <= 180.f;
3416 }
3417 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3418 // to a secant, i.e. convex.
3419 return SkScalarAbs(sweepAngle) <= 360.f;
3420}
3421
bsalomon21af9ca2016-08-25 12:29:23 -07003422void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3423 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3424 SkASSERT(!oval.isEmpty());
3425 SkASSERT(sweepAngle);
3426
3427 path->reset();
3428 path->setIsVolatile(true);
3429 path->setFillType(SkPath::kWinding_FillType);
3430 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3431 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003432 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003433 return;
3434 }
3435 if (useCenter) {
3436 path->moveTo(oval.centerX(), oval.centerY());
3437 }
Brian Salomone4949402018-04-26 15:22:04 -04003438 auto firstDir =
3439 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3440 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003441 // Arc to mods at 360 and drawArc is not supposed to.
3442 bool forceMoveTo = !useCenter;
3443 while (sweepAngle <= -360.f) {
3444 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3445 startAngle -= 180.f;
3446 path->arcTo(oval, startAngle, -180.f, false);
3447 startAngle -= 180.f;
3448 forceMoveTo = false;
3449 sweepAngle += 360.f;
3450 }
3451 while (sweepAngle >= 360.f) {
3452 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3453 startAngle += 180.f;
3454 path->arcTo(oval, startAngle, 180.f, false);
3455 startAngle += 180.f;
3456 forceMoveTo = false;
3457 sweepAngle -= 360.f;
3458 }
3459 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3460 if (useCenter) {
3461 path->close();
3462 }
Brian Salomone4949402018-04-26 15:22:04 -04003463 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3464 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003465}
Mike Reed0d7dac82017-02-02 17:45:56 -08003466
3467///////////////////////////////////////////////////////////////////////////////////////////////////
3468#include "SkNx.h"
3469
3470static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3471 SkScalar ts[2];
3472 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3473 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3474 SkASSERT(n >= 0 && n <= 2);
3475 for (int i = 0; i < n; ++i) {
3476 extremas[i] = SkEvalQuadAt(src, ts[i]);
3477 }
3478 extremas[n] = src[2];
3479 return n + 1;
3480}
3481
3482static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3483 SkConic conic(src[0], src[1], src[2], w);
3484 SkScalar ts[2];
3485 int n = conic.findXExtrema(ts);
3486 n += conic.findYExtrema(&ts[n]);
3487 SkASSERT(n >= 0 && n <= 2);
3488 for (int i = 0; i < n; ++i) {
3489 extremas[i] = conic.evalAt(ts[i]);
3490 }
3491 extremas[n] = src[2];
3492 return n + 1;
3493}
3494
3495static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3496 SkScalar ts[4];
3497 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3498 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3499 SkASSERT(n >= 0 && n <= 4);
3500 for (int i = 0; i < n; ++i) {
3501 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3502 }
3503 extremas[n] = src[3];
3504 return n + 1;
3505}
3506
Mike Reed8d3196b2017-02-03 11:34:13 -05003507SkRect SkPath::computeTightBounds() const {
3508 if (0 == this->countVerbs()) {
3509 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003510 }
3511
Mike Reed8d3196b2017-02-03 11:34:13 -05003512 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3513 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003514 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003515
Mike Reed0d7dac82017-02-02 17:45:56 -08003516 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3517 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003518 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003519
3520 // initial with the first MoveTo, so we don't have to check inside the switch
3521 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003522 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003523 for (;;) {
3524 int count = 0;
3525 switch (iter.next(pts)) {
3526 case SkPath::kMove_Verb:
3527 extremas[0] = pts[0];
3528 count = 1;
3529 break;
3530 case SkPath::kLine_Verb:
3531 extremas[0] = pts[1];
3532 count = 1;
3533 break;
3534 case SkPath::kQuad_Verb:
3535 count = compute_quad_extremas(pts, extremas);
3536 break;
3537 case SkPath::kConic_Verb:
3538 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3539 break;
3540 case SkPath::kCubic_Verb:
3541 count = compute_cubic_extremas(pts, extremas);
3542 break;
3543 case SkPath::kClose_Verb:
3544 break;
3545 case SkPath::kDone_Verb:
3546 goto DONE;
3547 }
3548 for (int i = 0; i < count; ++i) {
3549 Sk2s tmp = from_point(extremas[i]);
3550 min = Sk2s::Min(min, tmp);
3551 max = Sk2s::Max(max, tmp);
3552 }
3553 }
3554DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003555 SkRect bounds;
3556 min.store((SkPoint*)&bounds.fLeft);
3557 max.store((SkPoint*)&bounds.fRight);
3558 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003559}
Cary Clarkdf429f32017-11-08 11:44:31 -05003560
3561bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3562 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3563}
3564
3565bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3566 const SkPoint& p3, bool exact) {
3567 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3568 SkPointPriv::EqualsWithinTolerance(p2, p3);
3569}
3570
3571bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3572 const SkPoint& p3, const SkPoint& p4, bool exact) {
3573 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3574 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3575 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3576 SkPointPriv::EqualsWithinTolerance(p3, p4);
3577}