blob: af8cf9e0bfd408ff4b0b98209459bbb7f8a464db [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
Mike Reed6a388002018-10-16 13:13:09 -0400760void SkPath::incReserve(int inc) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761 SkDEBUGCODE(this->validate();)
Mike Reed6a388002018-10-16 13:13:09 -0400762 if (inc > 0) {
763 SkPathRef::Editor(&fPathRef, inc, inc);
764 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765 SkDEBUGCODE(this->validate();)
766}
767
Mike Reedb6317422018-08-15 10:23:39 -0400768SkPath& SkPath::moveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000769 SkDEBUGCODE(this->validate();)
770
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000771 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000773 // remember our index
774 fLastMoveToIndex = fPathRef->countPoints();
775
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700777
778 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400779 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780}
781
Mike Reedb6317422018-08-15 10:23:39 -0400782SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000783 SkPoint pt;
784 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400785 return this->moveTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786}
787
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000788void SkPath::injectMoveToIfNeeded() {
789 if (fLastMoveToIndex < 0) {
790 SkScalar x, y;
791 if (fPathRef->countVerbs() == 0) {
792 x = y = 0;
793 } else {
794 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
795 x = pt.fX;
796 y = pt.fY;
797 }
798 this->moveTo(x, y);
799 }
800}
801
Mike Reedb6317422018-08-15 10:23:39 -0400802SkPath& SkPath::lineTo(SkScalar x, SkScalar y) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 SkDEBUGCODE(this->validate();)
804
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000805 this->injectMoveToIfNeeded();
806
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000807 SkPathRef::Editor ed(&fPathRef);
808 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809
reed@google.comb54455e2011-05-16 14:16:04 +0000810 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400811 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812}
813
Mike Reedb6317422018-08-15 10:23:39 -0400814SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000815 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816 SkPoint pt;
817 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400818 return this->lineTo(pt.fX + x, pt.fY + y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819}
820
Mike Reedb6317422018-08-15 10:23:39 -0400821SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000822 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000823
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000824 this->injectMoveToIfNeeded();
825
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000826 SkPathRef::Editor ed(&fPathRef);
827 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828 pts[0].set(x1, y1);
829 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000830
reed@google.comb54455e2011-05-16 14:16:04 +0000831 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400832 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000833}
834
Mike Reedb6317422018-08-15 10:23:39 -0400835SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000836 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 SkPoint pt;
838 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400839 return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840}
841
Mike Reedb6317422018-08-15 10:23:39 -0400842SkPath& SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
843 SkScalar w) {
reed@google.com277c3f82013-05-31 15:17:50 +0000844 // check for <= 0 or NaN with this test
845 if (!(w > 0)) {
846 this->lineTo(x2, y2);
847 } else if (!SkScalarIsFinite(w)) {
848 this->lineTo(x1, y1);
849 this->lineTo(x2, y2);
850 } else if (SK_Scalar1 == w) {
851 this->quadTo(x1, y1, x2, y2);
852 } else {
853 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000854
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000855 this->injectMoveToIfNeeded();
856
reed@google.com277c3f82013-05-31 15:17:50 +0000857 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000858 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000859 pts[0].set(x1, y1);
860 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000861
reed@google.com277c3f82013-05-31 15:17:50 +0000862 DIRTY_AFTER_EDIT;
863 }
Mike Reedb6317422018-08-15 10:23:39 -0400864 return *this;
reed@google.com277c3f82013-05-31 15:17:50 +0000865}
866
Mike Reedb6317422018-08-15 10:23:39 -0400867SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
868 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000869 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000870 SkPoint pt;
871 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400872 return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000873}
874
Mike Reedb6317422018-08-15 10:23:39 -0400875SkPath& SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
876 SkScalar x3, SkScalar y3) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 SkDEBUGCODE(this->validate();)
878
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000879 this->injectMoveToIfNeeded();
880
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000881 SkPathRef::Editor ed(&fPathRef);
882 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 pts[0].set(x1, y1);
884 pts[1].set(x2, y2);
885 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886
reed@google.comb54455e2011-05-16 14:16:04 +0000887 DIRTY_AFTER_EDIT;
Mike Reedb6317422018-08-15 10:23:39 -0400888 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889}
890
Mike Reedb6317422018-08-15 10:23:39 -0400891SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
892 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000893 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000894 SkPoint pt;
895 this->getLastPt(&pt);
Mike Reedb6317422018-08-15 10:23:39 -0400896 return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
897 pt.fX + x3, pt.fY + y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898}
899
Mike Reedb6317422018-08-15 10:23:39 -0400900SkPath& SkPath::close() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901 SkDEBUGCODE(this->validate();)
902
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000903 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000905 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906 case kLine_Verb:
907 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000908 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000909 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000910 case kMove_Verb: {
911 SkPathRef::Editor ed(&fPathRef);
912 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000913 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000914 }
reed@google.com277c3f82013-05-31 15:17:50 +0000915 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000916 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000917 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000918 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000919 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000920 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000921 }
922 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000923
924 // signal that we need a moveTo to follow us (unless we're done)
925#if 0
926 if (fLastMoveToIndex >= 0) {
927 fLastMoveToIndex = ~fLastMoveToIndex;
928 }
929#else
930 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
931#endif
Mike Reedb6317422018-08-15 10:23:39 -0400932 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000933}
934
935///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000936
fmalitac08d53e2015-11-17 09:53:29 -0800937namespace {
938
939template <unsigned N>
940class PointIterator {
941public:
942 PointIterator(SkPath::Direction dir, unsigned startIndex)
943 : fCurrent(startIndex % N)
944 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
945
946 const SkPoint& current() const {
947 SkASSERT(fCurrent < N);
948 return fPts[fCurrent];
949 }
950
951 const SkPoint& next() {
952 fCurrent = (fCurrent + fAdvance) % N;
953 return this->current();
954 }
955
956protected:
957 SkPoint fPts[N];
958
959private:
960 unsigned fCurrent;
961 unsigned fAdvance;
962};
963
964class RectPointIterator : public PointIterator<4> {
965public:
966 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
967 : PointIterator(dir, startIndex) {
968
969 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
970 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
971 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
972 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
973 }
974};
975
976class OvalPointIterator : public PointIterator<4> {
977public:
978 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
979 : PointIterator(dir, startIndex) {
980
981 const SkScalar cx = oval.centerX();
982 const SkScalar cy = oval.centerY();
983
984 fPts[0] = SkPoint::Make(cx, oval.fTop);
985 fPts[1] = SkPoint::Make(oval.fRight, cy);
986 fPts[2] = SkPoint::Make(cx, oval.fBottom);
987 fPts[3] = SkPoint::Make(oval.fLeft, cy);
988 }
989};
990
991class RRectPointIterator : public PointIterator<8> {
992public:
993 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
994 : PointIterator(dir, startIndex) {
995
996 const SkRect& bounds = rrect.getBounds();
997 const SkScalar L = bounds.fLeft;
998 const SkScalar T = bounds.fTop;
999 const SkScalar R = bounds.fRight;
1000 const SkScalar B = bounds.fBottom;
1001
1002 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
1003 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
1004 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
1005 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
1006 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
1007 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
1008 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
1009 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
1010 }
1011};
1012
1013} // anonymous namespace
1014
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001015static void assert_known_direction(int dir) {
1016 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
1017}
1018
Mike Reedb6317422018-08-15 10:23:39 -04001019SkPath& SkPath::addRect(const SkRect& rect, Direction dir) {
1020 return this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021}
1022
Mike Reedb6317422018-08-15 10:23:39 -04001023SkPath& SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
reed@android.com8a1c16f2008-12-17 15:59:43 +00001024 SkScalar bottom, Direction dir) {
Mike Reedb6317422018-08-15 10:23:39 -04001025 return this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
fmalitac08d53e2015-11-17 09:53:29 -08001026}
1027
Mike Reedb6317422018-08-15 10:23:39 -04001028SkPath& SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001029 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001030 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001031 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001032 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001033 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001034
fmalitac08d53e2015-11-17 09:53:29 -08001035 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001036
fmalitac08d53e2015-11-17 09:53:29 -08001037 const int kVerbs = 5; // moveTo + 3x lineTo + close
1038 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001039
fmalitac08d53e2015-11-17 09:53:29 -08001040 RectPointIterator iter(rect, dir, startIndex);
1041
1042 this->moveTo(iter.current());
1043 this->lineTo(iter.next());
1044 this->lineTo(iter.next());
1045 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001046 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001047
1048 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
Mike Reedb6317422018-08-15 10:23:39 -04001049 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001050}
1051
Mike Reedb6317422018-08-15 10:23:39 -04001052SkPath& SkPath::addPoly(const SkPoint pts[], int count, bool close) {
reed@google.com744faba2012-05-29 19:54:52 +00001053 SkDEBUGCODE(this->validate();)
1054 if (count <= 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001055 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001056 }
1057
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001058 fLastMoveToIndex = fPathRef->countPoints();
1059
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001060 // +close makes room for the extra kClose_Verb
1061 SkPathRef::Editor ed(&fPathRef, count+close, count);
1062
1063 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001064 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001065 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1066 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001067 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001068
reed@google.com744faba2012-05-29 19:54:52 +00001069 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001070 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001071 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001072 }
1073
reed@google.com744faba2012-05-29 19:54:52 +00001074 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001075 SkDEBUGCODE(this->validate();)
Mike Reedb6317422018-08-15 10:23:39 -04001076 return *this;
reed@google.com744faba2012-05-29 19:54:52 +00001077}
1078
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079#include "SkGeometry.h"
1080
reedf90ea012015-01-29 12:03:58 -08001081static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1082 SkPoint* pt) {
1083 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084 // Chrome uses this path to move into and out of ovals. If not
1085 // treated as a special case the moves can distort the oval's
1086 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001087 pt->set(oval.fRight, oval.centerY());
1088 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 } else if (0 == oval.width() && 0 == oval.height()) {
1090 // Chrome will sometimes create 0 radius round rects. Having degenerate
1091 // quad segments in the path prevents the path from being recognized as
1092 // a rect.
1093 // TODO: optimizing the case where only one of width or height is zero
1094 // should also be considered. This case, however, doesn't seem to be
1095 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001096 pt->set(oval.fRight, oval.fTop);
1097 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001098 }
reedf90ea012015-01-29 12:03:58 -08001099 return false;
1100}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001101
reedd5d27d92015-02-09 13:54:43 -08001102// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1103//
1104static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1105 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1106 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1107 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001108
1109 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001110 loss in radians-conversion and/or sin/cos, we may end up with coincident
1111 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1112 of drawing a nearly complete circle (good).
1113 e.g. canvas.drawArc(0, 359.99, ...)
1114 -vs- canvas.drawArc(0, 359.9, ...)
1115 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001116 */
reedd5d27d92015-02-09 13:54:43 -08001117 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001118 SkScalar sw = SkScalarAbs(sweepAngle);
1119 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1120 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1121 // make a guess at a tiny angle (in radians) to tweak by
1122 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1123 // not sure how much will be enough, so we use a loop
1124 do {
1125 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001126 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1127 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001128 }
1129 }
reedd5d27d92015-02-09 13:54:43 -08001130 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1131}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001132
reed9e779d42015-02-17 11:43:14 -08001133/**
1134 * If this returns 0, then the caller should just line-to the singlePt, else it should
1135 * ignore singlePt and append the specified number of conics.
1136 */
reedd5d27d92015-02-09 13:54:43 -08001137static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001138 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1139 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001140 SkMatrix matrix;
1141
1142 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1143 matrix.postTranslate(oval.centerX(), oval.centerY());
1144
reed9e779d42015-02-17 11:43:14 -08001145 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1146 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001147 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001148 }
1149 return count;
reedd5d27d92015-02-09 13:54:43 -08001150}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001151
Mike Reedb6317422018-08-15 10:23:39 -04001152SkPath& SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001153 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001154 SkRRect rrect;
1155 rrect.setRectRadii(rect, (const SkVector*) radii);
Mike Reedb6317422018-08-15 10:23:39 -04001156 return this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001157}
1158
Mike Reedb6317422018-08-15 10:23:39 -04001159SkPath& SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001160 // legacy start indices: 6 (CW) and 7(CCW)
Mike Reedb6317422018-08-15 10:23:39 -04001161 return this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
fmalitac08d53e2015-11-17 09:53:29 -08001162}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001163
Mike Reedb6317422018-08-15 10:23:39 -04001164SkPath& SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1165 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001166
Mike Reedb6317422018-08-15 10:23:39 -04001167 bool isRRect = hasOnlyMoveTos();
1168 const SkRect& bounds = rrect.getBounds();
fmalitac08d53e2015-11-17 09:53:29 -08001169
Mike Reedb6317422018-08-15 10:23:39 -04001170 if (rrect.isRect() || rrect.isEmpty()) {
1171 // degenerate(rect) => radii points are collapsing
1172 this->addRect(bounds, dir, (startIndex + 1) / 2);
1173 } else if (rrect.isOval()) {
1174 // degenerate(oval) => line points are collapsing
1175 this->addOval(bounds, dir, startIndex / 2);
1176 } else {
1177 fFirstDirection = this->hasOnlyMoveTos() ?
1178 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
fmalitac08d53e2015-11-17 09:53:29 -08001179
Mike Reedb6317422018-08-15 10:23:39 -04001180 SkAutoPathBoundsUpdate apbu(this, bounds);
1181 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001182
Mike Reedb6317422018-08-15 10:23:39 -04001183 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1184 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1185 const SkScalar weight = SK_ScalarRoot2Over2;
fmalitac08d53e2015-11-17 09:53:29 -08001186
Mike Reedb6317422018-08-15 10:23:39 -04001187 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1188 const int kVerbs = startsWithConic
1189 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1190 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1191 this->incReserve(kVerbs);
fmalitac08d53e2015-11-17 09:53:29 -08001192
Mike Reedb6317422018-08-15 10:23:39 -04001193 RRectPointIterator rrectIter(rrect, dir, startIndex);
1194 // Corner iterator indices follow the collapsed radii model,
1195 // adjusted such that the start pt is "behind" the radii start pt.
1196 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1197 RectPointIterator rectIter(bounds, dir, rectStartIndex);
fmalitac08d53e2015-11-17 09:53:29 -08001198
Mike Reedb6317422018-08-15 10:23:39 -04001199 this->moveTo(rrectIter.current());
1200 if (startsWithConic) {
1201 for (unsigned i = 0; i < 3; ++i) {
fmalitac08d53e2015-11-17 09:53:29 -08001202 this->conicTo(rectIter.next(), rrectIter.next(), weight);
Mike Reedb6317422018-08-15 10:23:39 -04001203 this->lineTo(rrectIter.next());
fmalitac08d53e2015-11-17 09:53:29 -08001204 }
Mike Reedb6317422018-08-15 10:23:39 -04001205 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1206 // final lineTo handled by close().
1207 } else {
1208 for (unsigned i = 0; i < 4; ++i) {
1209 this->lineTo(rrectIter.next());
1210 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1211 }
fmalitac08d53e2015-11-17 09:53:29 -08001212 }
Mike Reedb6317422018-08-15 10:23:39 -04001213 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001214
Mike Reedb6317422018-08-15 10:23:39 -04001215 SkPathRef::Editor ed(&fPathRef);
1216 ed.setIsRRect(isRRect, dir, startIndex % 8);
1217
1218 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1219 }
1220
1221 SkDEBUGCODE(fPathRef->validate();)
1222 return *this;
reed@google.com4ed0fb72012-12-12 20:48:18 +00001223}
1224
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001225bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001226 int count = fPathRef->countVerbs();
1227 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1228 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001229 if (*verbs == kLine_Verb ||
1230 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001231 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001232 *verbs == kCubic_Verb) {
1233 return false;
1234 }
1235 ++verbs;
1236 }
1237 return true;
1238}
1239
Brian Osmana2318572017-07-10 15:09:26 -04001240bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1241 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001242 if (count < 2) {
1243 return true;
1244 }
Brian Osmana2318572017-07-10 15:09:26 -04001245 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001246 const SkPoint& first = *pts;
1247 for (int index = 1; index < count; ++index) {
1248 if (first != pts[index]) {
1249 return false;
1250 }
1251 }
1252 return true;
1253}
1254
Mike Reedb6317422018-08-15 10:23:39 -04001255SkPath& SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1256 Direction dir) {
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001257 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001258
humper@google.com75e3ca12013-04-08 21:44:11 +00001259 if (rx < 0 || ry < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001260 return *this;
humper@google.com75e3ca12013-04-08 21:44:11 +00001261 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001262
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001263 SkRRect rrect;
1264 rrect.setRectXY(rect, rx, ry);
Mike Reedb6317422018-08-15 10:23:39 -04001265 return this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001266}
1267
Mike Reedb6317422018-08-15 10:23:39 -04001268SkPath& SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001269 // legacy start index: 1
Mike Reedb6317422018-08-15 10:23:39 -04001270 return this->addOval(oval, dir, 1);
fmalitac08d53e2015-11-17 09:53:29 -08001271}
1272
Mike Reedb6317422018-08-15 10:23:39 -04001273SkPath& SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001274 assert_known_direction(dir);
1275
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001276 /* If addOval() is called after previous moveTo(),
1277 this path is still marked as an oval. This is used to
1278 fit into WebKit's calling sequences.
1279 We can't simply check isEmpty() in this case, as additional
1280 moveTo() would mark the path non empty.
1281 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001282 bool isOval = hasOnlyMoveTos();
1283 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001284 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001285 } else {
reed026beb52015-06-10 14:23:15 -07001286 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001287 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001288
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001289 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001290 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291
fmalitac08d53e2015-11-17 09:53:29 -08001292 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1293 const int kVerbs = 6; // moveTo + 4x conicTo + close
1294 this->incReserve(kVerbs);
1295
1296 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1297 // The corner iterator pts are tracking "behind" the oval/radii pts.
1298 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001299 const SkScalar weight = SK_ScalarRoot2Over2;
1300
fmalitac08d53e2015-11-17 09:53:29 -08001301 this->moveTo(ovalIter.current());
1302 for (unsigned i = 0; i < 4; ++i) {
1303 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001304 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001305 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001306
fmalitac08d53e2015-11-17 09:53:29 -08001307 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1308
robertphillips@google.com466310d2013-12-03 16:43:54 +00001309 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001310
bsalomon78d58d12016-05-27 09:17:04 -07001311 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
Mike Reedb6317422018-08-15 10:23:39 -04001312 return *this;
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001313}
1314
Mike Reedb6317422018-08-15 10:23:39 -04001315SkPath& SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001316 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001317 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 }
Mike Reedb6317422018-08-15 10:23:39 -04001319 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320}
1321
Mike Reedb6317422018-08-15 10:23:39 -04001322SkPath& SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1323 bool forceMoveTo) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 if (oval.width() < 0 || oval.height() < 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001325 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001326 }
1327
reedf90ea012015-01-29 12:03:58 -08001328 if (fPathRef->countVerbs() == 0) {
1329 forceMoveTo = true;
1330 }
1331
1332 SkPoint lonePt;
1333 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
Mike Reedb6317422018-08-15 10:23:39 -04001334 return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
reedf90ea012015-01-29 12:03:58 -08001335 }
1336
reedd5d27d92015-02-09 13:54:43 -08001337 SkVector startV, stopV;
1338 SkRotationDirection dir;
1339 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1340
reed9e779d42015-02-17 11:43:14 -08001341 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001342
Brian Salomone4949402018-04-26 15:22:04 -04001343 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1344 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1345 // arcs from the same oval.
1346 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1347 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001348 if (forceMoveTo) {
1349 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001350 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001351 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1352 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1353 this->lineTo(pt);
1354 }
1355 };
1356
xidachen6069dda2016-10-06 05:42:23 -07001357 // At this point, we know that the arc is not a lone point, but startV == stopV
1358 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1359 // cannot handle it.
1360 if (startV == stopV) {
1361 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1362 SkScalar radiusX = oval.width() / 2;
1363 SkScalar radiusY = oval.height() / 2;
1364 // We cannot use SkScalarSinCos function in the next line because
1365 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1366 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001367 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001368 // make sin(endAngle) to be 0 which will then draw a dot.
1369 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1370 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001371 addPt(singlePt);
Mike Reedb6317422018-08-15 10:23:39 -04001372 return *this;
xidachen6069dda2016-10-06 05:42:23 -07001373 }
1374
reedd5d27d92015-02-09 13:54:43 -08001375 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001376 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001377 if (count) {
1378 this->incReserve(count * 2 + 1);
1379 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001380 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001381 for (int i = 0; i < count; ++i) {
1382 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1383 }
reed9e779d42015-02-17 11:43:14 -08001384 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001385 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001386 }
Mike Reedb6317422018-08-15 10:23:39 -04001387 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001388}
1389
caryclark55d49052016-01-23 05:07:04 -08001390// This converts the SVG arc to conics.
1391// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1392// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1393// See also SVG implementation notes:
1394// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1395// Note that arcSweep bool value is flipped from the original implementation.
Mike Reedb6317422018-08-15 10:23:39 -04001396SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1397 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001398 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001399 SkPoint srcPts[2];
1400 this->getLastPt(&srcPts[0]);
1401 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1402 // joining the endpoints.
1403 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1404 if (!rx || !ry) {
Mike Reedb6317422018-08-15 10:23:39 -04001405 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001406 }
1407 // If the current point and target point for the arc are identical, it should be treated as a
1408 // zero length path. This ensures continuity in animations.
1409 srcPts[1].set(x, y);
1410 if (srcPts[0] == srcPts[1]) {
Mike Reedb6317422018-08-15 10:23:39 -04001411 return this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001412 }
1413 rx = SkScalarAbs(rx);
1414 ry = SkScalarAbs(ry);
1415 SkVector midPointDistance = srcPts[0] - srcPts[1];
1416 midPointDistance *= 0.5f;
1417
1418 SkMatrix pointTransform;
1419 pointTransform.setRotate(-angle);
1420
1421 SkPoint transformedMidPoint;
1422 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1423 SkScalar squareRx = rx * rx;
1424 SkScalar squareRy = ry * ry;
1425 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1426 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1427
1428 // Check if the radii are big enough to draw the arc, scale radii if not.
1429 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1430 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1431 if (radiiScale > 1) {
1432 radiiScale = SkScalarSqrt(radiiScale);
1433 rx *= radiiScale;
1434 ry *= radiiScale;
1435 }
1436
1437 pointTransform.setScale(1 / rx, 1 / ry);
1438 pointTransform.preRotate(-angle);
1439
1440 SkPoint unitPts[2];
1441 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1442 SkVector delta = unitPts[1] - unitPts[0];
1443
1444 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1445 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1446
1447 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1448 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1449 scaleFactor = -scaleFactor;
1450 }
1451 delta.scale(scaleFactor);
1452 SkPoint centerPoint = unitPts[0] + unitPts[1];
1453 centerPoint *= 0.5f;
1454 centerPoint.offset(-delta.fY, delta.fX);
1455 unitPts[0] -= centerPoint;
1456 unitPts[1] -= centerPoint;
1457 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1458 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1459 SkScalar thetaArc = theta2 - theta1;
1460 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1461 thetaArc += SK_ScalarPI * 2;
1462 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1463 thetaArc -= SK_ScalarPI * 2;
1464 }
1465 pointTransform.setRotate(angle);
1466 pointTransform.preScale(rx, ry);
1467
Cary Clark1acd3c72017-12-08 11:37:01 -05001468 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1469 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
caryclark55d49052016-01-23 05:07:04 -08001470 SkScalar thetaWidth = thetaArc / segments;
1471 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1472 if (!SkScalarIsFinite(t)) {
Mike Reedb6317422018-08-15 10:23:39 -04001473 return *this;
caryclark55d49052016-01-23 05:07:04 -08001474 }
1475 SkScalar startTheta = theta1;
1476 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001477 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1478 return scalar == SkScalarFloorToScalar(scalar);
1479 };
1480 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1481 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1482 scalar_is_integer(x) && scalar_is_integer(y);
Mike Reed16c92162018-10-01 20:41:38 -04001483
caryclark55d49052016-01-23 05:07:04 -08001484 for (int i = 0; i < segments; ++i) {
1485 SkScalar endTheta = startTheta + thetaWidth;
1486 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1487
1488 unitPts[1].set(cosEndTheta, sinEndTheta);
1489 unitPts[1] += centerPoint;
1490 unitPts[0] = unitPts[1];
1491 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1492 SkPoint mapped[2];
1493 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001494 /*
1495 Computing the arc width introduces rounding errors that cause arcs to start
1496 outside their marks. A round rect may lose convexity as a result. If the input
1497 values are on integers, place the conic on integers as well.
1498 */
Cary Clark1acd3c72017-12-08 11:37:01 -05001499 if (expectIntegers) {
1500 SkScalar* mappedScalars = &mapped[0].fX;
1501 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1502 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1503 }
1504 }
caryclark55d49052016-01-23 05:07:04 -08001505 this->conicTo(mapped[0], mapped[1], w);
1506 startTheta = endTheta;
1507 }
Mike Reedb6317422018-08-15 10:23:39 -04001508 return *this;
caryclark55d49052016-01-23 05:07:04 -08001509}
1510
Mike Reedb6317422018-08-15 10:23:39 -04001511SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1512 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
caryclark55d49052016-01-23 05:07:04 -08001513 SkPoint currentPoint;
1514 this->getLastPt(&currentPoint);
Mike Reedb6317422018-08-15 10:23:39 -04001515 return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
1516 currentPoint.fX + dx, currentPoint.fY + dy);
caryclark55d49052016-01-23 05:07:04 -08001517}
1518
Mike Reedb6317422018-08-15 10:23:39 -04001519SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001520 if (oval.isEmpty() || 0 == sweepAngle) {
Mike Reedb6317422018-08-15 10:23:39 -04001521 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 }
1523
1524 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1525
1526 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001527 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1528 // See SkPath::addOval() docs.
1529 SkScalar startOver90 = startAngle / 90.f;
1530 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1531 SkScalar error = startOver90 - startOver90I;
1532 if (SkScalarNearlyEqual(error, 0)) {
1533 // Index 1 is at startAngle == 0.
1534 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1535 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
Mike Reedb6317422018-08-15 10:23:39 -04001536 return this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1537 (unsigned) startIndex);
bsalomon1978ce22016-05-31 10:42:16 -07001538 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 }
Mike Reedb6317422018-08-15 10:23:39 -04001540 return this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541}
1542
1543/*
1544 Need to handle the case when the angle is sharp, and our computed end-points
1545 for the arc go behind pt1 and/or p2...
1546*/
Mike Reedb6317422018-08-15 10:23:39 -04001547SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001548 if (radius == 0) {
Mike Reedb6317422018-08-15 10:23:39 -04001549 return this->lineTo(x1, y1);
reeda8b326c2014-12-09 11:50:32 -08001550 }
1551
1552 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554 // need to know our prev pt so we can construct tangent vectors
1555 {
1556 SkPoint start;
1557 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001558 // Handle degenerate cases by adding a line to the first point and
1559 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560 before.setNormalize(x1 - start.fX, y1 - start.fY);
1561 after.setNormalize(x2 - x1, y2 - y1);
1562 }
reed@google.comabf15c12011-01-18 20:35:51 +00001563
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 SkScalar cosh = SkPoint::DotProduct(before, after);
1565 SkScalar sinh = SkPoint::CrossProduct(before, after);
1566
1567 if (SkScalarNearlyZero(sinh)) { // angle is too tight
Mike Reedb6317422018-08-15 10:23:39 -04001568 return this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001569 }
reed@google.comabf15c12011-01-18 20:35:51 +00001570
Mike Reeda99b6ce2017-02-04 11:04:26 -05001571 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572
Mike Reeda99b6ce2017-02-04 11:04:26 -05001573 SkScalar xx = x1 - dist * before.fX;
1574 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001575 after.setLength(dist);
1576 this->lineTo(xx, yy);
1577 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
Mike Reedb6317422018-08-15 10:23:39 -04001578 return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001579}
1580
1581///////////////////////////////////////////////////////////////////////////////
1582
Mike Reedb6317422018-08-15 10:23:39 -04001583SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001584 SkMatrix matrix;
1585
1586 matrix.setTranslate(dx, dy);
Mike Reedb6317422018-08-15 10:23:39 -04001587 return this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001588}
1589
Mike Reedc3d8a482018-09-12 10:08:40 -04001590SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) {
1591 // Detect if we're trying to add ourself
1592 const SkPath* src = &srcPath;
1593 SkTLazy<SkPath> tmp;
1594 if (this == src) {
1595 src = tmp.set(srcPath);
1596 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597
Mike Reedc3d8a482018-09-12 10:08:40 -04001598 SkPathRef::Editor(&fPathRef, src->countVerbs(), src->countPoints());
1599
1600 RawIter iter(*src);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 SkPoint pts[4];
1602 Verb verb;
1603
Cary Clark9480d822017-10-19 18:01:13 -04001604 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001605 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 while ((verb = iter.next(pts)) != kDone_Verb) {
1607 switch (verb) {
1608 case kMove_Verb:
1609 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001610 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1611 injectMoveToIfNeeded(); // In case last contour is closed
Cary Clark54ff3022018-08-17 10:58:23 -04001612 SkPoint lastPt;
1613 // don't add lineTo if it is degenerate
1614 if (fLastMoveToIndex < 0 || !this->getLastPt(&lastPt) || lastPt != pts[0]) {
1615 this->lineTo(pts[0]);
1616 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001617 } else {
1618 this->moveTo(pts[0]);
1619 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620 break;
1621 case kLine_Verb:
1622 proc(matrix, &pts[1], &pts[1], 1);
1623 this->lineTo(pts[1]);
1624 break;
1625 case kQuad_Verb:
1626 proc(matrix, &pts[1], &pts[1], 2);
1627 this->quadTo(pts[1], pts[2]);
1628 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001629 case kConic_Verb:
1630 proc(matrix, &pts[1], &pts[1], 2);
1631 this->conicTo(pts[1], pts[2], iter.conicWeight());
1632 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633 case kCubic_Verb:
1634 proc(matrix, &pts[1], &pts[1], 3);
1635 this->cubicTo(pts[1], pts[2], pts[3]);
1636 break;
1637 case kClose_Verb:
1638 this->close();
1639 break;
1640 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001641 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001642 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001643 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 }
Mike Reedb6317422018-08-15 10:23:39 -04001645 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646}
1647
1648///////////////////////////////////////////////////////////////////////////////
1649
reed@google.com277c3f82013-05-31 15:17:50 +00001650static int pts_in_verb(unsigned verb) {
1651 static const uint8_t gPtsInVerb[] = {
1652 1, // kMove
1653 1, // kLine
1654 2, // kQuad
1655 2, // kConic
1656 3, // kCubic
1657 0, // kClose
1658 0 // kDone
1659 };
1660
1661 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1662 return gPtsInVerb[verb];
1663}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664
reed@android.com8a1c16f2008-12-17 15:59:43 +00001665// ignore the last point of the 1st contour
Mike Reedb6317422018-08-15 10:23:39 -04001666SkPath& SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001667 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1668 if (!verbs) { // empty path returns nullptr
Mike Reedb6317422018-08-15 10:23:39 -04001669 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 }
caryclark51c56782016-11-07 05:09:28 -08001671 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1672 SkASSERT(verbsEnd[0] == kMove_Verb);
1673 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1674 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675
caryclark51c56782016-11-07 05:09:28 -08001676 while (verbs < verbsEnd) {
1677 uint8_t v = *verbs++;
1678 pts -= pts_in_verb(v);
1679 switch (v) {
1680 case kMove_Verb:
1681 // if the path has multiple contours, stop after reversing the last
Mike Reedb6317422018-08-15 10:23:39 -04001682 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001684 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001685 break;
1686 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001687 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001689 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001690 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001691 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001693 this->cubicTo(pts[2], pts[1], pts[0]);
1694 break;
1695 case kClose_Verb:
1696 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001697 break;
1698 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001699 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700 break;
1701 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 }
Mike Reedb6317422018-08-15 10:23:39 -04001703 return *this;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001704}
1705
Robert Phillips8051d382018-09-13 08:22:15 -04001706SkPath& SkPath::reverseAddPath(const SkPath& srcPath) {
1707 // Detect if we're trying to add ourself
1708 const SkPath* src = &srcPath;
1709 SkTLazy<SkPath> tmp;
1710 if (this == src) {
1711 src = tmp.set(srcPath);
1712 }
reed@google.com63d73742012-01-10 15:33:12 +00001713
Robert Phillipsf026d892018-09-14 11:07:00 -04001714 SkPathRef::Editor ed(&fPathRef, src->countVerbs(), src->countPoints());
Robert Phillips8051d382018-09-13 08:22:15 -04001715
1716 const SkPoint* pts = src->fPathRef->pointsEnd();
Robert Phillipsf026d892018-09-14 11:07:00 -04001717 // we will iterate through src's verbs backwards
Robert Phillips8051d382018-09-13 08:22:15 -04001718 const uint8_t* verbs = src->fPathRef->verbsMemBegin(); // points at the last verb
1719 const uint8_t* verbsEnd = src->fPathRef->verbs(); // points just past the first verb
1720 const SkScalar* conicWeights = src->fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001721
1722 bool needMove = true;
1723 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001724 while (verbs < verbsEnd) {
1725 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001726 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001727
1728 if (needMove) {
1729 --pts;
1730 this->moveTo(pts->fX, pts->fY);
1731 needMove = false;
1732 }
1733 pts -= n;
1734 switch (v) {
1735 case kMove_Verb:
1736 if (needClose) {
1737 this->close();
1738 needClose = false;
1739 }
1740 needMove = true;
1741 pts += 1; // so we see the point in "if (needMove)" above
1742 break;
1743 case kLine_Verb:
1744 this->lineTo(pts[0]);
1745 break;
1746 case kQuad_Verb:
1747 this->quadTo(pts[1], pts[0]);
1748 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001749 case kConic_Verb:
1750 this->conicTo(pts[1], pts[0], *--conicWeights);
1751 break;
reed@google.com63d73742012-01-10 15:33:12 +00001752 case kCubic_Verb:
1753 this->cubicTo(pts[2], pts[1], pts[0]);
1754 break;
1755 case kClose_Verb:
1756 needClose = true;
1757 break;
1758 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001759 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001760 }
1761 }
Mike Reedb6317422018-08-15 10:23:39 -04001762 return *this;
reed@google.com63d73742012-01-10 15:33:12 +00001763}
1764
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765///////////////////////////////////////////////////////////////////////////////
1766
1767void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1768 SkMatrix matrix;
1769
1770 matrix.setTranslate(dx, dy);
1771 this->transform(matrix, dst);
1772}
1773
reed@android.com8a1c16f2008-12-17 15:59:43 +00001774static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1775 int level = 2) {
1776 if (--level >= 0) {
1777 SkPoint tmp[7];
1778
1779 SkChopCubicAtHalf(pts, tmp);
1780 subdivide_cubic_to(path, &tmp[0], level);
1781 subdivide_cubic_to(path, &tmp[3], level);
1782 } else {
1783 path->cubicTo(pts[1], pts[2], pts[3]);
1784 }
1785}
1786
1787void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1788 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001789 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 dst = (SkPath*)this;
1791 }
1792
tomhudson@google.com8d430182011-06-06 19:11:19 +00001793 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 SkPath tmp;
1795 tmp.fFillType = fFillType;
1796
1797 SkPath::Iter iter(*this, false);
1798 SkPoint pts[4];
1799 SkPath::Verb verb;
1800
reed@google.com4a3b7142012-05-16 17:16:46 +00001801 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 switch (verb) {
1803 case kMove_Verb:
1804 tmp.moveTo(pts[0]);
1805 break;
1806 case kLine_Verb:
1807 tmp.lineTo(pts[1]);
1808 break;
1809 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001810 // promote the quad to a conic
1811 tmp.conicTo(pts[1], pts[2],
1812 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001814 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001815 tmp.conicTo(pts[1], pts[2],
1816 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001817 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 case kCubic_Verb:
1819 subdivide_cubic_to(&tmp, pts);
1820 break;
1821 case kClose_Verb:
1822 tmp.close();
1823 break;
1824 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001825 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826 break;
1827 }
1828 }
1829
1830 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001831 SkPathRef::Editor ed(&dst->fPathRef);
1832 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001833 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001835 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1836
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 if (this != dst) {
Robert Phillipsf026d892018-09-14 11:07:00 -04001838 dst->fLastMoveToIndex = fLastMoveToIndex;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001840 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001841 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001843
reed026beb52015-06-10 14:23:15 -07001844 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1845 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001846 } else {
1847 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001848 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1849 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001850 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001851 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1852 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001853 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001854 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001855 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001856 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001857 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001858 }
1859 }
1860
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 SkDEBUGCODE(dst->validate();)
1862 }
1863}
1864
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865///////////////////////////////////////////////////////////////////////////////
1866///////////////////////////////////////////////////////////////////////////////
1867
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868SkPath::Iter::Iter() {
1869#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001870 fPts = nullptr;
1871 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001872 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001874 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875#endif
1876 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001877 fVerbs = nullptr;
1878 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 fNeedClose = false;
1880}
1881
1882SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1883 this->setPath(path, forceClose);
1884}
1885
1886void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001887 fPts = path.fPathRef->points();
1888 fVerbs = path.fPathRef->verbs();
1889 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001890 fConicWeights = path.fPathRef->conicWeights();
1891 if (fConicWeights) {
1892 fConicWeights -= 1; // begin one behind
1893 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001895 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 fForceClose = SkToU8(forceClose);
1897 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001898 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899}
1900
1901bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001902 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 return false;
1904 }
1905 if (fForceClose) {
1906 return true;
1907 }
1908
1909 const uint8_t* verbs = fVerbs;
1910 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001911
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001912 if (kMove_Verb == *(verbs - 1)) {
1913 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 }
1915
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 while (verbs > stop) {
1917 // verbs points one beyond the current verb, decrement first.
1918 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 if (kMove_Verb == v) {
1920 break;
1921 }
1922 if (kClose_Verb == v) {
1923 return true;
1924 }
1925 }
1926 return false;
1927}
1928
1929SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001930 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001932 // A special case: if both points are NaN, SkPoint::operation== returns
1933 // false, but the iterator expects that they are treated as the same.
1934 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001935 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1936 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001937 return kClose_Verb;
1938 }
1939
reed@google.com9e25dbf2012-05-15 17:05:38 +00001940 pts[0] = fLastPt;
1941 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001942 fLastPt = fMoveTo;
1943 fCloseLine = true;
1944 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001945 } else {
1946 pts[0] = fMoveTo;
1947 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949}
1950
reed@google.com9e25dbf2012-05-15 17:05:38 +00001951const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 if (fSegmentState == kAfterMove_SegmentState) {
1953 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 }
Ben Wagnercee46e52018-06-12 16:30:29 -04001957
1958 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1959 // Set the first return pt to the last pt of the previous primitive.
1960 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961}
1962
caryclarke8c56662015-07-14 11:19:26 -07001963void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 // We need to step over anything that will not move the current draw point
1965 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001966 const uint8_t* lastMoveVerb = nullptr;
1967 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001968 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 SkPoint lastPt = fLastPt;
1970 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 switch (verb) {
1973 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 // Keep a record of this most recent move
1975 lastMoveVerb = fVerbs;
1976 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001977 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001978 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001979 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 fPts++;
1981 break;
1982
1983 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001984 // A close when we are in a segment is always valid except when it
1985 // follows a move which follows a segment.
1986 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 return;
1988 }
1989 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001990 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991 break;
1992
1993 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001994 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001995 if (lastMoveVerb) {
1996 fVerbs = lastMoveVerb;
1997 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001998 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001999 return;
2000 }
2001 return;
2002 }
2003 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002004 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002005 fPts++;
2006 break;
2007
reed@google.com277c3f82013-05-31 15:17:50 +00002008 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002009 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002010 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002011 if (lastMoveVerb) {
2012 fVerbs = lastMoveVerb;
2013 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002014 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002015 return;
2016 }
2017 return;
2018 }
2019 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002020 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002021 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00002022 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002023 break;
2024
2025 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07002026 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002027 if (lastMoveVerb) {
2028 fVerbs = lastMoveVerb;
2029 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08002030 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002031 return;
2032 }
2033 return;
2034 }
2035 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002036 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002037 fPts += 3;
2038 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002039
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002040 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002041 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002042 }
2043 }
2044}
2045
reed@google.com4a3b7142012-05-16 17:16:46 +00002046SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002047 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002048
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002050 // Close the curve if requested and if there is some curve to close
2051 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002052 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053 return kLine_Verb;
2054 }
2055 fNeedClose = false;
2056 return kClose_Verb;
2057 }
2058 return kDone_Verb;
2059 }
2060
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002061 // fVerbs is one beyond the current verb, decrement first
2062 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002063 const SkPoint* SK_RESTRICT srcPts = fPts;
2064 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065
2066 switch (verb) {
2067 case kMove_Verb:
2068 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002069 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 verb = this->autoClose(pts);
2071 if (verb == kClose_Verb) {
2072 fNeedClose = false;
2073 }
2074 return (Verb)verb;
2075 }
2076 if (fVerbs == fVerbStop) { // might be a trailing moveto
2077 return kDone_Verb;
2078 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002079 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002080 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002081 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002082 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002083 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002084 fNeedClose = fForceClose;
2085 break;
2086 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002087 pts[0] = this->cons_moveTo();
2088 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002089 fLastPt = srcPts[0];
2090 fCloseLine = false;
2091 srcPts += 1;
2092 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002093 case kConic_Verb:
2094 fConicWeights += 1;
2095 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002097 pts[0] = this->cons_moveTo();
2098 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002099 fLastPt = srcPts[1];
2100 srcPts += 2;
2101 break;
2102 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002103 pts[0] = this->cons_moveTo();
2104 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 fLastPt = srcPts[2];
2106 srcPts += 3;
2107 break;
2108 case kClose_Verb:
2109 verb = this->autoClose(pts);
2110 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002111 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002112 } else {
2113 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002114 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002116 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117 break;
2118 }
2119 fPts = srcPts;
2120 return (Verb)verb;
2121}
2122
2123///////////////////////////////////////////////////////////////////////////////
2124
Ben Wagner4d1955c2017-03-10 13:08:15 -05002125#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002126#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002127#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002128
reed@google.com51bbe752013-01-17 22:07:50 +00002129static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002130 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002131 str->append(label);
2132 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002133
reed@google.com51bbe752013-01-17 22:07:50 +00002134 const SkScalar* values = &pts[0].fX;
2135 count *= 2;
2136
2137 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002138 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002139 if (i < count - 1) {
2140 str->append(", ");
2141 }
2142 }
Mike Reed405b9d22018-01-09 15:11:08 -05002143 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002144 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002145 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002146 }
caryclark08fa28c2014-10-23 13:08:56 -07002147 str->append(");");
reede05fed02014-12-15 07:59:53 -08002148 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002149 str->append(" // ");
2150 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002151 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002152 if (i < count - 1) {
2153 str->append(", ");
2154 }
2155 }
2156 if (conicWeight >= 0) {
2157 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002158 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002159 }
2160 }
2161 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002162}
2163
caryclarke9562592014-09-15 09:26:09 -07002164void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002165 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 Iter iter(*this, forceClose);
2167 SkPoint pts[4];
2168 Verb verb;
2169
reed@google.com51bbe752013-01-17 22:07:50 +00002170 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002171 char const * const gFillTypeStrs[] = {
2172 "Winding",
2173 "EvenOdd",
2174 "InverseWinding",
2175 "InverseEvenOdd",
2176 };
2177 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2178 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002179 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002180 switch (verb) {
2181 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002182 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 break;
2184 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002185 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 break;
2187 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002188 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002189 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002190 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002191 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002192 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002193 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002194 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195 break;
2196 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002197 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002198 break;
2199 default:
2200 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2201 verb = kDone_Verb; // stop the loop
2202 break;
2203 }
caryclark1049f122015-04-20 08:31:59 -07002204 if (!wStream && builder.size()) {
2205 SkDebugf("%s", builder.c_str());
2206 builder.reset();
2207 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002208 }
caryclark66a5d8b2014-06-24 08:30:15 -07002209 if (wStream) {
2210 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002211 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212}
2213
reed@android.come522ca52009-11-23 20:10:41 +00002214void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002215 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002216}
2217
2218void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002219 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002220}
2221
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002222
Cary Clark0461e9f2017-08-25 15:13:38 -04002223bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002224 if ((fFillType & ~3) != 0) {
2225 return false;
2226 }
reed@google.comabf15c12011-01-18 20:35:51 +00002227
djsollen@google.com077348c2012-10-22 20:23:32 +00002228#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002229 if (!fBoundsIsDirty) {
2230 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002231
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002232 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002233 if (SkToBool(fIsFinite) != isFinite) {
2234 return false;
2235 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002236
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002237 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002238 // if we're empty, fBounds may be empty but translated, so we can't
2239 // necessarily compare to bounds directly
2240 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2241 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002242 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2243 return false;
2244 }
reed@android.come522ca52009-11-23 20:10:41 +00002245 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002246 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002247 if (!fBounds.isEmpty()) {
2248 return false;
2249 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002250 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002251 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002252 if (!fBounds.contains(bounds)) {
2253 return false;
2254 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002255 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002256 }
reed@android.come522ca52009-11-23 20:10:41 +00002257 }
2258 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002259#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002260 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002261}
reed@android.come522ca52009-11-23 20:10:41 +00002262
reed@google.com04863fa2011-05-15 04:08:24 +00002263///////////////////////////////////////////////////////////////////////////////
2264
reed@google.com0b7b9822011-05-16 12:29:27 +00002265static int sign(SkScalar x) { return x < 0; }
2266#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002267
robertphillipsc506e302014-09-16 09:43:31 -07002268enum DirChange {
2269 kLeft_DirChange,
2270 kRight_DirChange,
2271 kStraight_DirChange,
2272 kBackwards_DirChange,
2273
2274 kInvalid_DirChange
2275};
2276
2277
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002278static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002279 // The error epsilon was empirically derived; worse case round rects
2280 // with a mid point outset by 2x float epsilon in tests had an error
2281 // of 12.
2282 const int epsilon = 16;
2283 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2284 return false;
2285 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002286 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002287 int aBits = SkFloatAs2sCompliment(compA);
2288 int bBits = SkFloatAs2sCompliment(compB);
2289 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002290}
2291
caryclarkb4216502015-03-02 13:02:34 -08002292static bool approximately_zero_when_compared_to(double x, double y) {
2293 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002294}
2295
caryclarkb4216502015-03-02 13:02:34 -08002296
reed@google.com04863fa2011-05-15 04:08:24 +00002297// only valid for a single contour
2298struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002299 Convexicator()
2300 : fPtCount(0)
2301 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002302 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002303 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002304 , fIsCurve(false)
2305 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002306 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002307 // warnings
djsollen2f124632016-04-29 13:53:05 -07002308 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002309 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002310 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002311 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002312 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002313
2314 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002315 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002316 }
2317
2318 SkPath::Convexity getConvexity() const { return fConvexity; }
2319
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002320 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002321 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002322
reed@google.com04863fa2011-05-15 04:08:24 +00002323 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002324 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002325 return;
2326 }
2327
2328 if (0 == fPtCount) {
2329 fCurrPt = pt;
2330 ++fPtCount;
2331 } else {
2332 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002333 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002334 if (!SkScalarIsFinite(lengthSqd)) {
2335 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002336 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002337 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002338 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002339 fCurrPt = pt;
2340 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002341 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002342 } else {
2343 SkASSERT(fPtCount > 2);
2344 this->addVec(vec);
2345 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002346
reed@google.com85b6e392011-05-15 20:25:17 +00002347 int sx = sign(vec.fX);
2348 int sy = sign(vec.fY);
2349 fDx += (sx != fSx);
2350 fDy += (sy != fSy);
2351 fSx = sx;
2352 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002353
reed@google.com85b6e392011-05-15 20:25:17 +00002354 if (fDx > 3 || fDy > 3) {
2355 fConvexity = SkPath::kConcave_Convexity;
2356 }
reed@google.com04863fa2011-05-15 04:08:24 +00002357 }
2358 }
2359 }
2360
2361 void close() {
2362 if (fPtCount > 2) {
2363 this->addVec(fFirstVec);
2364 }
2365 }
2366
caryclarkb4216502015-03-02 13:02:34 -08002367 DirChange directionChange(const SkVector& curVec) {
2368 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2369
2370 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2371 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2372 largest = SkTMax(largest, -smallest);
2373
2374 if (!almost_equal(largest, largest + cross)) {
2375 int sign = SkScalarSignAsInt(cross);
2376 if (sign) {
2377 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2378 }
2379 }
2380
2381 if (cross) {
2382 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2383 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2384 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2385 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2386 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2387 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2388 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2389 if (sign) {
2390 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2391 }
2392 }
2393 }
2394
Cary Clarkdf429f32017-11-08 11:44:31 -05002395 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2396 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2397 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2398 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002399 fLastVec.dot(curVec) < 0.0f) {
2400 return kBackwards_DirChange;
2401 }
2402
2403 return kStraight_DirChange;
2404 }
2405
Cary Clarkc8323aa2017-08-25 08:04:43 -04002406 bool hasBackwards() const {
2407 return fBackwards;
2408 }
caryclarkb4216502015-03-02 13:02:34 -08002409
caryclarkd3d1a982014-12-08 04:57:38 -08002410 bool isFinite() const {
2411 return fIsFinite;
2412 }
2413
caryclark5ccef572015-03-02 10:07:56 -08002414 void setCurve(bool isCurve) {
2415 fIsCurve = isCurve;
2416 }
2417
reed@google.com04863fa2011-05-15 04:08:24 +00002418private:
2419 void addVec(const SkVector& vec) {
2420 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002421 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002422 switch (dir) {
2423 case kLeft_DirChange: // fall through
2424 case kRight_DirChange:
2425 if (kInvalid_DirChange == fExpectedDir) {
2426 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002427 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2428 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002429 } else if (dir != fExpectedDir) {
2430 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002431 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002432 }
2433 fLastVec = vec;
2434 break;
2435 case kStraight_DirChange:
2436 break;
2437 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002438 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002439 // If any of the subsequent dir is non-backward, it'll be concave.
2440 // Otherwise, it's still convex.
2441 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002442 }
robertphillipsc506e302014-09-16 09:43:31 -07002443 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002444 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002445 break;
2446 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002447 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002448 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002449 }
2450 }
2451
caryclarkb4216502015-03-02 13:02:34 -08002452 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002453 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002454 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002455 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2456 // value with the current vec is deemed to be of a significant value.
2457 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002458 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002459 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002460 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002461 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002462 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002463 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002464 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002465 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002466};
2467
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002468SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002469 // Sometimes we think we need to calculate convexity but another thread already did.
2470 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2471 return c;
2472 }
2473
reed@google.com04863fa2011-05-15 04:08:24 +00002474 SkPoint pts[4];
2475 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002476 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002477
2478 int contourCount = 0;
2479 int count;
2480 Convexicator state;
2481
caryclarkd3d1a982014-12-08 04:57:38 -08002482 if (!isFinite()) {
2483 return kUnknown_Convexity;
2484 }
Brian Osman205a1262017-09-18 13:13:48 +00002485 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002486 switch (verb) {
2487 case kMove_Verb:
2488 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002489 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002490 return kConcave_Convexity;
2491 }
2492 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002493 // fall through
2494 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002495 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002496 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002497 break;
caryclark5ccef572015-03-02 10:07:56 -08002498 case kQuad_Verb:
2499 // fall through
2500 case kConic_Verb:
2501 // fall through
2502 case kCubic_Verb:
2503 count = 2 + (kCubic_Verb == verb);
2504 // As an additional enhancement, this could set curve true only
2505 // if the curve is nonlinear
2506 state.setCurve(true);
2507 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002508 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002509 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002510 state.close();
2511 count = 0;
2512 break;
2513 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002514 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002515 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002516 return kConcave_Convexity;
2517 }
2518
2519 for (int i = 1; i <= count; i++) {
2520 state.addPt(pts[i]);
2521 }
2522 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002523 if (!state.isFinite()) {
2524 return kUnknown_Convexity;
2525 }
reed@google.com04863fa2011-05-15 04:08:24 +00002526 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002527 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002528 return kConcave_Convexity;
2529 }
2530 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002531 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002532 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002533 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2534 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2535 fConvexity = Convexity::kConcave_Convexity;
2536 } else {
2537 fFirstDirection = state.getFirstDirection();
2538 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002539 }
2540 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002541}
reed@google.com69a99432012-01-10 18:00:10 +00002542
2543///////////////////////////////////////////////////////////////////////////////
2544
2545class ContourIter {
2546public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002547 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002548
2549 bool done() const { return fDone; }
2550 // if !done() then these may be called
2551 int count() const { return fCurrPtCount; }
2552 const SkPoint* pts() const { return fCurrPt; }
2553 void next();
2554
2555private:
2556 int fCurrPtCount;
2557 const SkPoint* fCurrPt;
2558 const uint8_t* fCurrVerb;
2559 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002560 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002561 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002562 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002563};
2564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002565ContourIter::ContourIter(const SkPathRef& pathRef) {
2566 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002567 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002568 fCurrPt = pathRef.points();
2569 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002570 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002571 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002572 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002573 this->next();
2574}
2575
2576void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002577 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002578 fDone = true;
2579 }
2580 if (fDone) {
2581 return;
2582 }
2583
2584 // skip pts of prev contour
2585 fCurrPt += fCurrPtCount;
2586
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002587 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002588 int ptCount = 1; // moveTo
2589 const uint8_t* verbs = fCurrVerb;
2590
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002591 for (--verbs; verbs > fStopVerbs; --verbs) {
2592 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002593 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002594 goto CONTOUR_END;
2595 case SkPath::kLine_Verb:
2596 ptCount += 1;
2597 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002598 case SkPath::kConic_Verb:
2599 fCurrConicWeight += 1;
2600 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002601 case SkPath::kQuad_Verb:
2602 ptCount += 2;
2603 break;
2604 case SkPath::kCubic_Verb:
2605 ptCount += 3;
2606 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002607 case SkPath::kClose_Verb:
2608 break;
2609 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002610 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002611 break;
2612 }
2613 }
2614CONTOUR_END:
2615 fCurrPtCount = ptCount;
2616 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002617 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002618}
2619
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002620// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002621static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002622 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2623 // We may get 0 when the above subtracts underflow. We expect this to be
2624 // very rare and lazily promote to double.
2625 if (0 == cross) {
2626 double p0x = SkScalarToDouble(p0.fX);
2627 double p0y = SkScalarToDouble(p0.fY);
2628
2629 double p1x = SkScalarToDouble(p1.fX);
2630 double p1y = SkScalarToDouble(p1.fY);
2631
2632 double p2x = SkScalarToDouble(p2.fX);
2633 double p2y = SkScalarToDouble(p2.fY);
2634
2635 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2636 (p1y - p0y) * (p2x - p0x));
2637
2638 }
2639 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002640}
2641
reed@google.comc1ea60a2012-01-31 15:15:36 +00002642// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002643static int find_max_y(const SkPoint pts[], int count) {
2644 SkASSERT(count > 0);
2645 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002646 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002647 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002648 SkScalar y = pts[i].fY;
2649 if (y > max) {
2650 max = y;
2651 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002652 }
2653 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002654 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002655}
2656
reed@google.comcabaf1d2012-01-11 21:03:05 +00002657static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2658 int i = index;
2659 for (;;) {
2660 i = (i + inc) % n;
2661 if (i == index) { // we wrapped around, so abort
2662 break;
2663 }
2664 if (pts[index] != pts[i]) { // found a different point, success!
2665 break;
2666 }
2667 }
2668 return i;
2669}
2670
reed@google.comc1ea60a2012-01-31 15:15:36 +00002671/**
2672 * Starting at index, and moving forward (incrementing), find the xmin and
2673 * xmax of the contiguous points that have the same Y.
2674 */
2675static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2676 int* maxIndexPtr) {
2677 const SkScalar y = pts[index].fY;
2678 SkScalar min = pts[index].fX;
2679 SkScalar max = min;
2680 int minIndex = index;
2681 int maxIndex = index;
2682 for (int i = index + 1; i < n; ++i) {
2683 if (pts[i].fY != y) {
2684 break;
2685 }
2686 SkScalar x = pts[i].fX;
2687 if (x < min) {
2688 min = x;
2689 minIndex = i;
2690 } else if (x > max) {
2691 max = x;
2692 maxIndex = i;
2693 }
2694 }
2695 *maxIndexPtr = maxIndex;
2696 return minIndex;
2697}
2698
reed026beb52015-06-10 14:23:15 -07002699static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2700 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002701}
2702
reed@google.comac8543f2012-01-30 20:51:25 +00002703/*
2704 * We loop through all contours, and keep the computed cross-product of the
2705 * contour that contained the global y-max. If we just look at the first
2706 * contour, we may find one that is wound the opposite way (correctly) since
2707 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2708 * that is outer most (or at least has the global y-max) before we can consider
2709 * its cross product.
2710 */
reed026beb52015-06-10 14:23:15 -07002711bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002712 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2713 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002714 return true;
2715 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002716
2717 // don't want to pay the cost for computing this if it
2718 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002719 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2720 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002721 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002722 return false;
2723 }
reed@google.com69a99432012-01-10 18:00:10 +00002724
reed026beb52015-06-10 14:23:15 -07002725 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002726
reed@google.comac8543f2012-01-30 20:51:25 +00002727 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002728 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002729 SkScalar ymaxCross = 0;
2730
reed@google.com69a99432012-01-10 18:00:10 +00002731 for (; !iter.done(); iter.next()) {
2732 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002733 if (n < 3) {
2734 continue;
2735 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002736
reed@google.comcabaf1d2012-01-11 21:03:05 +00002737 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002738 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002739 int index = find_max_y(pts, n);
2740 if (pts[index].fY < ymax) {
2741 continue;
2742 }
2743
2744 // If there is more than 1 distinct point at the y-max, we take the
2745 // x-min and x-max of them and just subtract to compute the dir.
2746 if (pts[(index + 1) % n].fY == pts[index].fY) {
2747 int maxIndex;
2748 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2749 if (minIndex == maxIndex) {
2750 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002751 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002752 SkASSERT(pts[minIndex].fY == pts[index].fY);
2753 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2754 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2755 // we just subtract the indices, and let that auto-convert to
2756 // SkScalar, since we just want - or + to signal the direction.
2757 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002758 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002759 TRY_CROSSPROD:
2760 // Find a next and prev index to use for the cross-product test,
2761 // but we try to find pts that form non-zero vectors from pts[index]
2762 //
2763 // Its possible that we can't find two non-degenerate vectors, so
2764 // we have to guard our search (e.g. all the pts could be in the
2765 // same place).
2766
2767 // we pass n - 1 instead of -1 so we don't foul up % operator by
2768 // passing it a negative LH argument.
2769 int prev = find_diff_pt(pts, index, n, n - 1);
2770 if (prev == index) {
2771 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002772 continue;
2773 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002774 int next = find_diff_pt(pts, index, n, 1);
2775 SkASSERT(next != index);
2776 cross = cross_prod(pts[prev], pts[index], pts[next]);
2777 // if we get a zero and the points are horizontal, then we look at the spread in
2778 // x-direction. We really should continue to walk away from the degeneracy until
2779 // there is a divergence.
2780 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2781 // construct the subtract so we get the correct Direction below
2782 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002783 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002784 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002786 if (cross) {
2787 // record our best guess so far
2788 ymax = pts[index].fY;
2789 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002790 }
2791 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002792 if (ymaxCross) {
2793 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002794 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002795 return true;
2796 } else {
2797 return false;
2798 }
reed@google.comac8543f2012-01-30 20:51:25 +00002799}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800
2801///////////////////////////////////////////////////////////////////////////////
2802
caryclark9aacd902015-12-14 08:38:09 -08002803static bool between(SkScalar a, SkScalar b, SkScalar c) {
2804 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2805 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2806 return (a - b) * (c - b) <= 0;
2807}
2808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2810 SkScalar t) {
2811 SkScalar A = c3 + 3*(c1 - c2) - c0;
2812 SkScalar B = 3*(c2 - c1 - c1 + c0);
2813 SkScalar C = 3*(c1 - c0);
2814 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002815 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816}
2817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818template <size_t N> static void find_minmax(const SkPoint pts[],
2819 SkScalar* minPtr, SkScalar* maxPtr) {
2820 SkScalar min, max;
2821 min = max = pts[0].fX;
2822 for (size_t i = 1; i < N; ++i) {
2823 min = SkMinScalar(min, pts[i].fX);
2824 max = SkMaxScalar(max, pts[i].fX);
2825 }
2826 *minPtr = min;
2827 *maxPtr = max;
2828}
2829
caryclark9cb5d752015-12-18 04:35:24 -08002830static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2831 if (start.fY == end.fY) {
2832 return between(start.fX, x, end.fX) && x != end.fX;
2833 } else {
2834 return x == start.fX && y == start.fY;
2835 }
2836}
2837
caryclark9aacd902015-12-14 08:38:09 -08002838static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002839 SkScalar y0 = pts[0].fY;
2840 SkScalar y3 = pts[3].fY;
2841
2842 int dir = 1;
2843 if (y0 > y3) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002844 using std::swap;
2845 swap(y0, y3);
caryclark9cb5d752015-12-18 04:35:24 -08002846 dir = -1;
2847 }
2848 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 return 0;
2850 }
caryclark9cb5d752015-12-18 04:35:24 -08002851 if (checkOnCurve(x, y, pts[0], pts[3])) {
2852 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002853 return 0;
2854 }
caryclark9cb5d752015-12-18 04:35:24 -08002855 if (y == y3) {
2856 return 0;
2857 }
2858
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002859 // quickreject or quickaccept
2860 SkScalar min, max;
2861 find_minmax<4>(pts, &min, &max);
2862 if (x < min) {
2863 return 0;
2864 }
2865 if (x > max) {
2866 return dir;
2867 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002868
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002869 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002870 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002871 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002872 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002873 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002874 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002875 if (SkScalarNearlyEqual(xt, x)) {
2876 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2877 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002878 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002879 }
2880 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 return xt < x ? dir : 0;
2882}
2883
caryclark9aacd902015-12-14 08:38:09 -08002884static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 SkPoint dst[10];
2886 int n = SkChopCubicAtYExtrema(pts, dst);
2887 int w = 0;
2888 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002889 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890 }
2891 return w;
2892}
2893
caryclark9aacd902015-12-14 08:38:09 -08002894static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2895 SkASSERT(src);
2896 SkASSERT(t >= 0 && t <= 1);
2897 SkScalar src2w = src[2] * w;
2898 SkScalar C = src[0];
2899 SkScalar A = src[4] - 2 * src2w + C;
2900 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002901 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002902}
2903
2904
2905static double conic_eval_denominator(SkScalar w, SkScalar t) {
2906 SkScalar B = 2 * (w - 1);
2907 SkScalar C = 1;
2908 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002909 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002910}
2911
2912static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2913 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 SkScalar y0 = pts[0].fY;
2915 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002916
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002917 int dir = 1;
2918 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002919 using std::swap;
2920 swap(y0, y2);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002921 dir = -1;
2922 }
caryclark9aacd902015-12-14 08:38:09 -08002923 if (y < y0 || y > y2) {
2924 return 0;
2925 }
caryclark9cb5d752015-12-18 04:35:24 -08002926 if (checkOnCurve(x, y, pts[0], pts[2])) {
2927 *onCurveCount += 1;
2928 return 0;
2929 }
caryclark9aacd902015-12-14 08:38:09 -08002930 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002931 return 0;
2932 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002933
caryclark9aacd902015-12-14 08:38:09 -08002934 SkScalar roots[2];
2935 SkScalar A = pts[2].fY;
2936 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2937 SkScalar C = pts[0].fY;
2938 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2939 B -= C; // B = b*w - w * yCept + yCept - a
2940 C -= y;
2941 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2942 SkASSERT(n <= 1);
2943 SkScalar xt;
2944 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002945 // zero roots are returned only when y0 == y
2946 // Need [0] if dir == 1
2947 // and [2] if dir == -1
2948 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002949 } else {
2950 SkScalar t = roots[0];
2951 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2952 }
2953 if (SkScalarNearlyEqual(xt, x)) {
2954 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2955 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002956 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002957 }
2958 }
2959 return xt < x ? dir : 0;
2960}
2961
2962static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2963 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2964 if (y0 == y1) {
2965 return true;
2966 }
2967 if (y0 < y1) {
2968 return y1 <= y2;
2969 } else {
2970 return y1 >= y2;
2971 }
2972}
2973
2974static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2975 int* onCurveCount) {
2976 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002977 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002978 // If the data points are very large, the conic may not be monotonic but may also
2979 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002980 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2981 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2982 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002983 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2984 }
2985 return w;
2986}
2987
2988static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2989 SkScalar y0 = pts[0].fY;
2990 SkScalar y2 = pts[2].fY;
2991
2992 int dir = 1;
2993 if (y0 > y2) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04002994 using std::swap;
2995 swap(y0, y2);
caryclark9aacd902015-12-14 08:38:09 -08002996 dir = -1;
2997 }
2998 if (y < y0 || y > y2) {
2999 return 0;
3000 }
caryclark9cb5d752015-12-18 04:35:24 -08003001 if (checkOnCurve(x, y, pts[0], pts[2])) {
3002 *onCurveCount += 1;
3003 return 0;
3004 }
caryclark9aacd902015-12-14 08:38:09 -08003005 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08003006 return 0;
3007 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 // bounds check on X (not required. is it faster?)
3009#if 0
3010 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
3011 return 0;
3012 }
3013#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003014
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 SkScalar roots[2];
3016 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3017 2 * (pts[1].fY - pts[0].fY),
3018 pts[0].fY - y,
3019 roots);
3020 SkASSERT(n <= 1);
3021 SkScalar xt;
3022 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08003023 // zero roots are returned only when y0 == y
3024 // Need [0] if dir == 1
3025 // and [2] if dir == -1
3026 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003027 } else {
3028 SkScalar t = roots[0];
3029 SkScalar C = pts[0].fX;
3030 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3031 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003032 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003033 }
caryclark9aacd902015-12-14 08:38:09 -08003034 if (SkScalarNearlyEqual(xt, x)) {
3035 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
3036 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08003037 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003038 }
3039 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003040 return xt < x ? dir : 0;
3041}
3042
caryclark9aacd902015-12-14 08:38:09 -08003043static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003044 SkPoint dst[5];
3045 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003046
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003047 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3048 n = SkChopQuadAtYExtrema(pts, dst);
3049 pts = dst;
3050 }
caryclark9aacd902015-12-14 08:38:09 -08003051 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003052 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003053 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003054 }
3055 return w;
3056}
3057
caryclark9aacd902015-12-14 08:38:09 -08003058static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003059 SkScalar x0 = pts[0].fX;
3060 SkScalar y0 = pts[0].fY;
3061 SkScalar x1 = pts[1].fX;
3062 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003063
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003064 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003065
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003066 int dir = 1;
3067 if (y0 > y1) {
Ben Wagnerf08d1d02018-06-18 15:11:00 -04003068 using std::swap;
3069 swap(y0, y1);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003070 dir = -1;
3071 }
caryclark9aacd902015-12-14 08:38:09 -08003072 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003073 return 0;
3074 }
caryclark9cb5d752015-12-18 04:35:24 -08003075 if (checkOnCurve(x, y, pts[0], pts[1])) {
3076 *onCurveCount += 1;
3077 return 0;
3078 }
3079 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003080 return 0;
3081 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003082 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003083
caryclark9aacd902015-12-14 08:38:09 -08003084 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003085 // zero cross means the point is on the line, and since the case where
3086 // y of the query point is at the end point is handled above, we can be
3087 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003088 if (x != x1 || y != pts[1].fY) {
3089 *onCurveCount += 1;
3090 }
caryclark9aacd902015-12-14 08:38:09 -08003091 dir = 0;
3092 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003093 dir = 0;
3094 }
3095 return dir;
3096}
3097
caryclark9aacd902015-12-14 08:38:09 -08003098static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3099 SkTDArray<SkVector>* tangents) {
3100 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3101 && !between(pts[2].fY, y, pts[3].fY)) {
3102 return;
3103 }
3104 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3105 && !between(pts[2].fX, x, pts[3].fX)) {
3106 return;
3107 }
3108 SkPoint dst[10];
3109 int n = SkChopCubicAtYExtrema(pts, dst);
3110 for (int i = 0; i <= n; ++i) {
3111 SkPoint* c = &dst[i * 3];
3112 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003113 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003114 continue;
mbarbella276e6332016-05-31 14:44:01 -07003115 }
caryclark9aacd902015-12-14 08:38:09 -08003116 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3117 if (!SkScalarNearlyEqual(x, xt)) {
3118 continue;
3119 }
3120 SkVector tangent;
3121 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
Mike Reed5edcd312018-08-08 11:23:41 -04003122 tangents->push_back(tangent);
caryclark9aacd902015-12-14 08:38:09 -08003123 }
3124}
3125
3126static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3127 SkTDArray<SkVector>* tangents) {
3128 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3129 return;
3130 }
3131 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3132 return;
3133 }
3134 SkScalar roots[2];
3135 SkScalar A = pts[2].fY;
3136 SkScalar B = pts[1].fY * w - y * w + y;
3137 SkScalar C = pts[0].fY;
3138 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3139 B -= C; // B = b*w - w * yCept + yCept - a
3140 C -= y;
3141 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3142 for (int index = 0; index < n; ++index) {
3143 SkScalar t = roots[index];
3144 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3145 if (!SkScalarNearlyEqual(x, xt)) {
3146 continue;
3147 }
3148 SkConic conic(pts, w);
Mike Reed5edcd312018-08-08 11:23:41 -04003149 tangents->push_back(conic.evalTangentAt(t));
caryclark9aacd902015-12-14 08:38:09 -08003150 }
3151}
3152
3153static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3154 SkTDArray<SkVector>* tangents) {
3155 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3156 return;
3157 }
3158 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3159 return;
3160 }
3161 SkScalar roots[2];
3162 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3163 2 * (pts[1].fY - pts[0].fY),
3164 pts[0].fY - y,
3165 roots);
3166 for (int index = 0; index < n; ++index) {
3167 SkScalar t = roots[index];
3168 SkScalar C = pts[0].fX;
3169 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3170 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003171 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003172 if (!SkScalarNearlyEqual(x, xt)) {
3173 continue;
3174 }
Mike Reed5edcd312018-08-08 11:23:41 -04003175 tangents->push_back(SkEvalQuadTangentAt(pts, t));
caryclark9aacd902015-12-14 08:38:09 -08003176 }
3177}
3178
3179static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3180 SkTDArray<SkVector>* tangents) {
3181 SkScalar y0 = pts[0].fY;
3182 SkScalar y1 = pts[1].fY;
3183 if (!between(y0, y, y1)) {
3184 return;
3185 }
3186 SkScalar x0 = pts[0].fX;
3187 SkScalar x1 = pts[1].fX;
3188 if (!between(x0, x, x1)) {
3189 return;
3190 }
3191 SkScalar dx = x1 - x0;
3192 SkScalar dy = y1 - y0;
3193 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3194 return;
3195 }
3196 SkVector v;
3197 v.set(dx, dy);
Mike Reed5edcd312018-08-08 11:23:41 -04003198 tangents->push_back(v);
caryclark9aacd902015-12-14 08:38:09 -08003199}
3200
reed@google.com4db592c2013-10-30 17:39:43 +00003201static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3202 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3203}
3204
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003205bool SkPath::contains(SkScalar x, SkScalar y) const {
3206 bool isInverse = this->isInverseFillType();
3207 if (this->isEmpty()) {
3208 return isInverse;
3209 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003210
reed@google.com4db592c2013-10-30 17:39:43 +00003211 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003212 return isInverse;
3213 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003214
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003215 SkPath::Iter iter(*this, true);
3216 bool done = false;
3217 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003218 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003219 do {
3220 SkPoint pts[4];
3221 switch (iter.next(pts, false)) {
3222 case SkPath::kMove_Verb:
3223 case SkPath::kClose_Verb:
3224 break;
3225 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003226 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003227 break;
3228 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003229 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003230 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003231 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003232 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003233 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003234 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003235 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003236 break;
3237 case SkPath::kDone_Verb:
3238 done = true;
3239 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003240 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003241 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003242 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3243 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3244 if (evenOddFill) {
3245 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003246 }
caryclark9aacd902015-12-14 08:38:09 -08003247 if (w) {
3248 return !isInverse;
3249 }
3250 if (onCurveCount <= 1) {
3251 return SkToBool(onCurveCount) ^ isInverse;
3252 }
3253 if ((onCurveCount & 1) || evenOddFill) {
3254 return SkToBool(onCurveCount & 1) ^ isInverse;
3255 }
halcanary9d524f22016-03-29 09:03:52 -07003256 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003257 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3258 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003259 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003260 SkTDArray<SkVector> tangents;
3261 do {
3262 SkPoint pts[4];
3263 int oldCount = tangents.count();
3264 switch (iter.next(pts, false)) {
3265 case SkPath::kMove_Verb:
3266 case SkPath::kClose_Verb:
3267 break;
3268 case SkPath::kLine_Verb:
3269 tangent_line(pts, x, y, &tangents);
3270 break;
3271 case SkPath::kQuad_Verb:
3272 tangent_quad(pts, x, y, &tangents);
3273 break;
3274 case SkPath::kConic_Verb:
3275 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3276 break;
3277 case SkPath::kCubic_Verb:
3278 tangent_cubic(pts, x, y, &tangents);
3279 break;
3280 case SkPath::kDone_Verb:
3281 done = true;
3282 break;
3283 }
3284 if (tangents.count() > oldCount) {
3285 int last = tangents.count() - 1;
3286 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003287 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003288 tangents.remove(last);
3289 } else {
3290 for (int index = 0; index < last; ++index) {
3291 const SkVector& test = tangents[index];
3292 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003293 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3294 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003295 tangents.remove(last);
3296 tangents.removeShuffle(index);
3297 break;
3298 }
3299 }
3300 }
3301 }
3302 } while (!done);
3303 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003304}
fmalitaaa0df4e2015-12-01 09:13:23 -08003305
3306int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3307 SkScalar w, SkPoint pts[], int pow2) {
3308 const SkConic conic(p0, p1, p2, w);
3309 return conic.chopIntoQuadsPOW2(pts, pow2);
3310}
bsalomonedc743a2016-06-01 09:42:31 -07003311
3312bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3313 unsigned* start) {
3314 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3315 return false;
3316 }
3317 SkPath::RawIter iter(path);
3318 SkPoint verbPts[4];
3319 SkPath::Verb v;
3320 SkPoint rectPts[5];
3321 int rectPtCnt = 0;
3322 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3323 switch (v) {
3324 case SkPath::kMove_Verb:
3325 if (0 != rectPtCnt) {
3326 return false;
3327 }
3328 rectPts[0] = verbPts[0];
3329 ++rectPtCnt;
3330 break;
3331 case SkPath::kLine_Verb:
3332 if (5 == rectPtCnt) {
3333 return false;
3334 }
3335 rectPts[rectPtCnt] = verbPts[1];
3336 ++rectPtCnt;
3337 break;
3338 case SkPath::kClose_Verb:
3339 if (4 == rectPtCnt) {
3340 rectPts[4] = rectPts[0];
3341 rectPtCnt = 5;
3342 }
3343 break;
3344 default:
3345 return false;
3346 }
3347 }
3348 if (rectPtCnt < 5) {
3349 return false;
3350 }
3351 if (rectPts[0] != rectPts[4]) {
3352 return false;
3353 }
bsalomon057ae8a2016-07-24 05:37:26 -07003354 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3355 // and pts 1 and 2 the opposite vertical or horizontal edge).
3356 bool vec03IsVertical;
3357 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3358 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3359 // Make sure it has non-zero width and height
3360 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003361 return false;
3362 }
bsalomon057ae8a2016-07-24 05:37:26 -07003363 vec03IsVertical = true;
3364 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3365 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3366 // Make sure it has non-zero width and height
3367 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3368 return false;
3369 }
3370 vec03IsVertical = false;
3371 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003372 return false;
3373 }
bsalomon057ae8a2016-07-24 05:37:26 -07003374 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3375 // set if it is on the bottom edge.
3376 unsigned sortFlags =
3377 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3378 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3379 switch (sortFlags) {
3380 case 0b00:
3381 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3382 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3383 *start = 0;
3384 break;
3385 case 0b01:
3386 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3387 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3388 *start = 1;
3389 break;
3390 case 0b10:
3391 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3392 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3393 *start = 3;
3394 break;
3395 case 0b11:
3396 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3397 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3398 *start = 2;
3399 break;
bsalomonedc743a2016-06-01 09:42:31 -07003400 }
bsalomonedc743a2016-06-01 09:42:31 -07003401 return true;
3402}
bsalomon21af9ca2016-08-25 12:29:23 -07003403
Brian Salomone4949402018-04-26 15:22:04 -04003404bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3405 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3406 // This gets converted to an oval.
3407 return true;
3408 }
3409 if (useCenter) {
3410 // This is a pie wedge. It's convex if the angle is <= 180.
3411 return SkScalarAbs(sweepAngle) <= 180.f;
3412 }
3413 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3414 // to a secant, i.e. convex.
3415 return SkScalarAbs(sweepAngle) <= 360.f;
3416}
3417
bsalomon21af9ca2016-08-25 12:29:23 -07003418void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3419 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3420 SkASSERT(!oval.isEmpty());
3421 SkASSERT(sweepAngle);
3422
3423 path->reset();
3424 path->setIsVolatile(true);
3425 path->setFillType(SkPath::kWinding_FillType);
3426 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3427 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003428 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003429 return;
3430 }
3431 if (useCenter) {
3432 path->moveTo(oval.centerX(), oval.centerY());
3433 }
Brian Salomone4949402018-04-26 15:22:04 -04003434 auto firstDir =
3435 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3436 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003437 // Arc to mods at 360 and drawArc is not supposed to.
3438 bool forceMoveTo = !useCenter;
3439 while (sweepAngle <= -360.f) {
3440 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3441 startAngle -= 180.f;
3442 path->arcTo(oval, startAngle, -180.f, false);
3443 startAngle -= 180.f;
3444 forceMoveTo = false;
3445 sweepAngle += 360.f;
3446 }
3447 while (sweepAngle >= 360.f) {
3448 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3449 startAngle += 180.f;
3450 path->arcTo(oval, startAngle, 180.f, false);
3451 startAngle += 180.f;
3452 forceMoveTo = false;
3453 sweepAngle -= 360.f;
3454 }
3455 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3456 if (useCenter) {
3457 path->close();
3458 }
Brian Salomone4949402018-04-26 15:22:04 -04003459 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3460 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003461}
Mike Reed0d7dac82017-02-02 17:45:56 -08003462
3463///////////////////////////////////////////////////////////////////////////////////////////////////
3464#include "SkNx.h"
3465
3466static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3467 SkScalar ts[2];
3468 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3469 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3470 SkASSERT(n >= 0 && n <= 2);
3471 for (int i = 0; i < n; ++i) {
3472 extremas[i] = SkEvalQuadAt(src, ts[i]);
3473 }
3474 extremas[n] = src[2];
3475 return n + 1;
3476}
3477
3478static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3479 SkConic conic(src[0], src[1], src[2], w);
3480 SkScalar ts[2];
3481 int n = conic.findXExtrema(ts);
3482 n += conic.findYExtrema(&ts[n]);
3483 SkASSERT(n >= 0 && n <= 2);
3484 for (int i = 0; i < n; ++i) {
3485 extremas[i] = conic.evalAt(ts[i]);
3486 }
3487 extremas[n] = src[2];
3488 return n + 1;
3489}
3490
3491static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3492 SkScalar ts[4];
3493 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3494 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3495 SkASSERT(n >= 0 && n <= 4);
3496 for (int i = 0; i < n; ++i) {
3497 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3498 }
3499 extremas[n] = src[3];
3500 return n + 1;
3501}
3502
Mike Reed8d3196b2017-02-03 11:34:13 -05003503SkRect SkPath::computeTightBounds() const {
3504 if (0 == this->countVerbs()) {
3505 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003506 }
3507
Mike Reed8d3196b2017-02-03 11:34:13 -05003508 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3509 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003510 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003511
Mike Reed0d7dac82017-02-02 17:45:56 -08003512 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3513 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003514 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003515
3516 // initial with the first MoveTo, so we don't have to check inside the switch
3517 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003518 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003519 for (;;) {
3520 int count = 0;
3521 switch (iter.next(pts)) {
3522 case SkPath::kMove_Verb:
3523 extremas[0] = pts[0];
3524 count = 1;
3525 break;
3526 case SkPath::kLine_Verb:
3527 extremas[0] = pts[1];
3528 count = 1;
3529 break;
3530 case SkPath::kQuad_Verb:
3531 count = compute_quad_extremas(pts, extremas);
3532 break;
3533 case SkPath::kConic_Verb:
3534 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3535 break;
3536 case SkPath::kCubic_Verb:
3537 count = compute_cubic_extremas(pts, extremas);
3538 break;
3539 case SkPath::kClose_Verb:
3540 break;
3541 case SkPath::kDone_Verb:
3542 goto DONE;
3543 }
3544 for (int i = 0; i < count; ++i) {
3545 Sk2s tmp = from_point(extremas[i]);
3546 min = Sk2s::Min(min, tmp);
3547 max = Sk2s::Max(max, tmp);
3548 }
3549 }
3550DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003551 SkRect bounds;
3552 min.store((SkPoint*)&bounds.fLeft);
3553 max.store((SkPoint*)&bounds.fRight);
3554 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003555}
Cary Clarkdf429f32017-11-08 11:44:31 -05003556
3557bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3558 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3559}
3560
3561bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3562 const SkPoint& p3, bool exact) {
3563 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3564 SkPointPriv::EqualsWithinTolerance(p2, p3);
3565}
3566
3567bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3568 const SkPoint& p3, const SkPoint& p4, bool exact) {
3569 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3570 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3571 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3572 SkPointPriv::EqualsWithinTolerance(p3, p4);
3573}