blob: 510efd6d9d48c4c7806f0f9c73eb33810ca97bbc [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
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050019#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020
Mike Reeda99b6ce2017-02-04 11:04:26 -050021static float poly_eval(float A, float B, float C, float t) {
22 return (A * t + B) * t + C;
23}
24
25static float poly_eval(float A, float B, float C, float D, float t) {
26 return ((A * t + B) * t + C) * t + D;
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029////////////////////////////////////////////////////////////////////////////
30
reed@google.com3563c9e2011-11-14 19:34:57 +000031/**
32 * Path.bounds is defined to be the bounds of all the control points.
33 * If we called bounds.join(r) we would skip r if r was empty, which breaks
34 * our promise. Hence we have a custom joiner that doesn't look at emptiness
35 */
36static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
37 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
38 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
39 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
40 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
41}
42
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043static bool is_degenerate(const SkPath& path) {
44 SkPath::Iter iter(path, false);
45 SkPoint pts[4];
46 return SkPath::kDone_Verb == iter.next(pts);
47}
48
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049class SkAutoDisableDirectionCheck {
50public:
51 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070052 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070056 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057 }
58
59private:
reed026beb52015-06-10 14:23:15 -070060 SkPath* fPath;
61 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000063#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065/* This guy's constructor/destructor bracket a path editing operation. It is
66 used when we know the bounds of the amount we are going to add to the path
67 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000070 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000071 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000072
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000073 It also notes if the path was originally degenerate, and if so, sets
74 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000075 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 */
77class SkAutoPathBoundsUpdate {
78public:
79 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
80 this->init(path);
81 }
82
83 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
84 SkScalar right, SkScalar bottom) {
85 fRect.set(left, top, right, bottom);
86 this->init(path);
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000090 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
91 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050092 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 }
95 }
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 SkPath* fPath;
99 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 // Cannot use fRect for our bounds unless we know it is sorted
106 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000108 // Mark the path's bounds as dirty if (1) they are, or (2) the path
109 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 if (fHasValidBounds && !fEmpty) {
113 joinNoEmptyChecks(&fRect, fPath->getBounds());
114 }
115 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 }
117};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000118#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000139SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800140 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700142 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400143 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144}
145
146void SkPath::resetFields() {
147 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000148 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000150 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700151 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000152
153 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700154 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 this->copyFields(that);
160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 }
174 SkDEBUGCODE(this->validate();)
175 return *this;
176}
177
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178void SkPath::copyFields(const SkPath& that) {
179 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000180 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400183 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400184
185 // Non-atomic assignment of atomic values.
186 fConvexity .store(that.fConvexity .load());
187 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000193 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000194 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700199 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000200 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400203
204 // Non-atomic swaps of atomic values.
205 Convexity c = fConvexity.load();
206 fConvexity.store(that.fConvexity.load());
207 that.fConvexity.store(c);
208
209 uint8_t fd = fFirstDirection.load();
210 fFirstDirection.store(that.fFirstDirection.load());
211 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
caryclark8e7b19d2016-02-18 04:11:48 -0800215bool SkPath::isInterpolatable(const SkPath& compare) const {
216 int count = fPathRef->countVerbs();
217 if (count != compare.fPathRef->countVerbs()) {
218 return false;
219 }
220 if (!count) {
221 return true;
222 }
223 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
224 count)) {
225 return false;
226 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800227 return !fPathRef->countWeights() ||
228 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800229 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
230}
231
232bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500233 int pointCount = fPathRef->countPoints();
234 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800235 return false;
236 }
Cary Clark7487ec82018-03-06 09:30:46 -0500237 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800238 return true;
239 }
caryclark8e7b19d2016-02-18 04:11:48 -0800240 out->reset();
241 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700242 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return true;
244}
245
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246static inline bool check_edge_against_rect(const SkPoint& p0,
247 const SkPoint& p1,
248 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700249 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 const SkPoint* edgeBegin;
251 SkVector v;
reed026beb52015-06-10 14:23:15 -0700252 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 v = p1 - p0;
254 edgeBegin = &p0;
255 } else {
256 v = p0 - p1;
257 edgeBegin = &p1;
258 }
259 if (v.fX || v.fY) {
260 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500261 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
262 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
263 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
264 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
266 return false;
267 }
268 }
269 return true;
270}
271
272bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
273 // This only handles non-degenerate convex paths currently.
274 if (kConvex_Convexity != this->getConvexity()) {
275 return false;
276 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000277
reed026beb52015-06-10 14:23:15 -0700278 SkPathPriv::FirstDirection direction;
279 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 return false;
281 }
282
283 SkPoint firstPt;
284 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500285 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkPath::Verb verb;
287 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400288 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 int nextPt = -1;
294 switch (verb) {
295 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(++moveCnt);
298 firstPt = prevPt = pts[0];
299 break;
300 case kLine_Verb:
301 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 break;
305 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000306 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 2;
310 break;
311 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 3;
315 break;
316 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 break;
319 default:
320 SkDEBUGFAIL("unknown verb");
321 }
322 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800323 if (SkPath::kConic_Verb == verb) {
324 SkConic orig;
325 orig.set(pts, iter.conicWeight());
326 SkPoint quadPts[5];
327 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800328 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800329
330 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
331 return false;
332 }
333 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
334 return false;
335 }
336 } else {
337 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
338 return false;
339 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400345 if (segmentCount) {
346 return check_edge_against_rect(prevPt, firstPt, rect, direction);
347 }
348 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000349}
350
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000351uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800353#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400354 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
355 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000356#endif
357 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000358}
djsollen@google.come63793a2012-03-21 15:39:03 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360void SkPath::reset() {
361 SkDEBUGCODE(this->validate();)
362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000364 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
367void SkPath::rewind() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
fsb1475b02016-01-20 09:51:07 -0800374bool SkPath::isLastContourClosed() const {
375 int verbCount = fPathRef->countVerbs();
376 if (0 == verbCount) {
377 return false;
378 }
379 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
380}
381
reed@google.com7e6c4d12012-05-10 14:05:43 +0000382bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000385 if (2 == verbCount) {
386 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
387 if (kLine_Verb == fPathRef->atVerb(1)) {
388 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391 line[0] = pts[0];
392 line[1] = pts[1];
393 }
394 return true;
395 }
396 }
397 return false;
398}
399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400/*
401 Determines if path is a rect by keeping track of changes in direction
402 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 The direction is computed such that:
405 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000408 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410A rectangle cycles up/right/down/left or up/left/down/right.
411
412The test fails if:
413 The path is closed, and followed by a line.
414 A second move creates a new endpoint.
415 A diagonal line is parsed.
416 There's more than four changes of direction.
417 There's a discontinuity on the line (e.g., a move in the middle)
418 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 *The rectangle doesn't complete a cycle.
422 *The final point isn't equal to the first point.
423
424 *These last two conditions we relax if we have a 3-edge path that would
425 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427It's OK if the path has:
428 Several colinear line segments composing a rectangle side.
429 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431The direction takes advantage of the corners found since opposite sides
432must travel in opposite directions.
433
434FIXME: Allow colinear quads and cubics to be treated like lines.
435FIXME: If the API passes fill-only, return true if the filled stroke
436 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000437
Cary Clark48c464a2018-04-16 12:06:07 -0400438 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000439 0x1 is set if the segment is horizontal
440 0x2 is set if the segment is moving to the right or down
441 thus:
442 two directions are opposites iff (dirA ^ dirB) == 0x2
443 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000444
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446static int rect_make_dir(SkScalar dx, SkScalar dy) {
447 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
448}
Cary Clark88ba9712018-04-12 14:00:24 -0400449
caryclark@google.comf68154a2012-11-21 15:18:06 +0000450bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400451 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400453 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400454 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400455 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
456 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400457 SkPoint firstCorner;
458 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000459 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400460 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400461 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400462 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000463 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700465 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000466 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000467 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700468 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
469 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700473 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400475 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400476 lastPt = pts;
477 }
Cary Clark88ba9712018-04-12 14:00:24 -0400478 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
479 SkVector lineDelta = lineEnd - lineStart;
480 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 return false; // diagonal
482 }
Cary Clark1eece782018-04-20 10:14:41 -0400483 if (!lineDelta.isFinite()) {
484 return false; // path contains infinity or NaN
485 }
Cary Clark88ba9712018-04-12 14:00:24 -0400486 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 break; // single point on side OK
488 }
Cary Clark48c464a2018-04-16 12:06:07 -0400489 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000490 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400491 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 corners = 1;
493 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400494 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000495 break;
496 }
497 if (closedOrMoved) {
498 return false; // closed followed by a line
499 }
Cary Clark48c464a2018-04-16 12:06:07 -0400500 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000501 break; // colinear with first
502 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400504 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400505 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400506 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400507 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400508 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 break; // colinear segment
510 }
Cary Clark48c464a2018-04-16 12:06:07 -0400511 directions[corners++] = nextDirection;
512 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400513 switch (corners) {
514 case 2:
515 firstCorner = lineStart;
516 break;
517 case 3:
518 if ((directions[0] ^ directions[2]) != 2) {
519 return false;
520 }
521 thirdCorner = lineEnd;
522 break;
523 case 4:
524 if ((directions[1] ^ directions[3]) != 2) {
525 return false;
526 }
527 break;
528 default:
529 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000530 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400531 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000532 break;
533 }
534 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000535 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000536 case kCubic_Verb:
537 return false; // quadratic, cubic not allowed
538 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400539 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700540 insertClose = true;
541 *currVerb -= 1; // try move again afterwards
542 goto addMissingClose;
543 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400544 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400545 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400546 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400547 closeXY = *firstPt - *lastPt;
548 if (closeXY.fX && closeXY.fY) {
549 return false; // we're diagonal, abort
550 }
Cary Clark8540e112018-04-11 14:30:27 -0400551 }
Cary Clark88ba9712018-04-12 14:00:24 -0400552 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000553 closedOrMoved = true;
554 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000555 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000556 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000557 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700560addMissingClose:
561 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000562 }
563 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400564 if (corners < 3 || corners > 4) {
565 return false;
566 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000567 if (savePts) {
568 *ptsPtr = savePts;
569 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400570 // check if close generates diagonal
571 closeXY = *firstPt - *lastPt;
572 if (closeXY.fX && closeXY.fY) {
573 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400574 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400575 if (rect) {
576 rect->set(firstCorner, thirdCorner);
577 }
578 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000579 *isClosed = autoClose;
580 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400581 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400582 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000583 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400584 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000585}
586
robertphillips4f662e62014-12-29 14:06:51 -0800587bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 SkDEBUGCODE(this->validate();)
589 int currVerb = 0;
590 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400591 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000592}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000593
caryclark95bc5f32015-04-08 08:34:15 -0700594bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595 SkDEBUGCODE(this->validate();)
596 int currVerb = 0;
597 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000598 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400599 SkRect testRects[2];
600 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000601 return false;
602 }
Cary Clark5c715182018-04-09 16:07:11 -0400603 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000604 if (testRects[0].contains(testRects[1])) {
605 if (rects) {
606 rects[0] = testRects[0];
607 rects[1] = testRects[1];
608 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000609 if (dirs) {
610 dirs[0] = testDirs[0];
611 dirs[1] = testDirs[1];
612 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000613 return true;
614 }
615 if (testRects[1].contains(testRects[0])) {
616 if (rects) {
617 rects[0] = testRects[1];
618 rects[1] = testRects[0];
619 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000620 if (dirs) {
621 dirs[0] = testDirs[1];
622 dirs[1] = testDirs[0];
623 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000624 return true;
625 }
626 }
627 return false;
628}
629
Mike Reed0c3137c2018-02-20 13:57:05 -0500630bool SkPath::isOval(SkRect* bounds) const {
631 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
632}
633
634bool SkPath::isRRect(SkRRect* rrect) const {
635 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
636}
637
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638int SkPath::countPoints() const {
639 return fPathRef->countPoints();
640}
641
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000642int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 SkDEBUGCODE(this->validate();)
644
645 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000646 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000647 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800648 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650}
651
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000652SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
654 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000655 }
656 return SkPoint::Make(0, 0);
657}
658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659int SkPath::countVerbs() const {
660 return fPathRef->countVerbs();
661}
662
663static inline void copy_verbs_reverse(uint8_t* inorderDst,
664 const uint8_t* reversedSrc,
665 int count) {
666 for (int i = 0; i < count; ++i) {
667 inorderDst[i] = reversedSrc[~i];
668 }
669}
670
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000671int SkPath::getVerbs(uint8_t dst[], int max) const {
672 SkDEBUGCODE(this->validate();)
673
674 SkASSERT(max >= 0);
675 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 int count = SkMin32(max, fPathRef->countVerbs());
677 copy_verbs_reverse(dst, fPathRef->verbs(), count);
678 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000679}
680
reed@google.com294dd7b2011-10-11 11:58:32 +0000681bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682 SkDEBUGCODE(this->validate();)
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000685 if (count > 0) {
686 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000687 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000689 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000691 if (lastPt) {
692 lastPt->set(0, 0);
693 }
694 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695}
696
caryclarkaec25102015-04-29 08:28:30 -0700697void SkPath::setPt(int index, SkScalar x, SkScalar y) {
698 SkDEBUGCODE(this->validate();)
699
700 int count = fPathRef->countPoints();
701 if (count <= index) {
702 return;
703 } else {
704 SkPathRef::Editor ed(&fPathRef);
705 ed.atPoint(index)->set(x, y);
706 }
707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709void SkPath::setLastPt(SkScalar x, SkScalar y) {
710 SkDEBUGCODE(this->validate();)
711
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 if (count == 0) {
714 this->moveTo(x, y);
715 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000716 SkPathRef::Editor ed(&fPathRef);
717 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 }
719}
720
reed@google.com04863fa2011-05-15 04:08:24 +0000721void SkPath::setConvexity(Convexity c) {
722 if (fConvexity != c) {
723 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000724 }
725}
726
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727//////////////////////////////////////////////////////////////////////////////
728// Construction methods
729
reed026beb52015-06-10 14:23:15 -0700730#define DIRTY_AFTER_EDIT \
731 do { \
732 fConvexity = kUnknown_Convexity; \
733 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000734 } while (0)
735
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736void SkPath::incReserve(U16CPU inc) {
737 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000738 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739 SkDEBUGCODE(this->validate();)
740}
741
742void SkPath::moveTo(SkScalar x, SkScalar y) {
743 SkDEBUGCODE(this->validate();)
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000747 // remember our index
748 fLastMoveToIndex = fPathRef->countPoints();
749
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700751
752 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753}
754
755void SkPath::rMoveTo(SkScalar x, SkScalar y) {
756 SkPoint pt;
757 this->getLastPt(&pt);
758 this->moveTo(pt.fX + x, pt.fY + y);
759}
760
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000761void SkPath::injectMoveToIfNeeded() {
762 if (fLastMoveToIndex < 0) {
763 SkScalar x, y;
764 if (fPathRef->countVerbs() == 0) {
765 x = y = 0;
766 } else {
767 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
768 x = pt.fX;
769 y = pt.fY;
770 }
771 this->moveTo(x, y);
772 }
773}
774
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775void SkPath::lineTo(SkScalar x, SkScalar y) {
776 SkDEBUGCODE(this->validate();)
777
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000778 this->injectMoveToIfNeeded();
779
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000780 SkPathRef::Editor ed(&fPathRef);
781 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782
reed@google.comb54455e2011-05-16 14:16:04 +0000783 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784}
785
786void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000787 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 SkPoint pt;
789 this->getLastPt(&pt);
790 this->lineTo(pt.fX + x, pt.fY + y);
791}
792
793void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
794 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000795
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000796 this->injectMoveToIfNeeded();
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 SkPathRef::Editor ed(&fPathRef);
799 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 pts[0].set(x1, y1);
801 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000802
reed@google.comb54455e2011-05-16 14:16:04 +0000803 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804}
805
806void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000807 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 SkPoint pt;
809 this->getLastPt(&pt);
810 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
811}
812
reed@google.com277c3f82013-05-31 15:17:50 +0000813void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
814 SkScalar w) {
815 // check for <= 0 or NaN with this test
816 if (!(w > 0)) {
817 this->lineTo(x2, y2);
818 } else if (!SkScalarIsFinite(w)) {
819 this->lineTo(x1, y1);
820 this->lineTo(x2, y2);
821 } else if (SK_Scalar1 == w) {
822 this->quadTo(x1, y1, x2, y2);
823 } else {
824 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000825
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826 this->injectMoveToIfNeeded();
827
reed@google.com277c3f82013-05-31 15:17:50 +0000828 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000829 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000830 pts[0].set(x1, y1);
831 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000832
reed@google.com277c3f82013-05-31 15:17:50 +0000833 DIRTY_AFTER_EDIT;
834 }
835}
836
837void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
838 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000839 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000840 SkPoint pt;
841 this->getLastPt(&pt);
842 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
843}
844
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
846 SkScalar x3, SkScalar y3) {
847 SkDEBUGCODE(this->validate();)
848
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000849 this->injectMoveToIfNeeded();
850
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 SkPathRef::Editor ed(&fPathRef);
852 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 pts[0].set(x1, y1);
854 pts[1].set(x2, y2);
855 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856
reed@google.comb54455e2011-05-16 14:16:04 +0000857 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858}
859
860void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
861 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000862 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 SkPoint pt;
864 this->getLastPt(&pt);
865 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
866 pt.fX + x3, pt.fY + y3);
867}
868
869void SkPath::close() {
870 SkDEBUGCODE(this->validate();)
871
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000872 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 case kLine_Verb:
876 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000877 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 case kMove_Verb: {
880 SkPathRef::Editor ed(&fPathRef);
881 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 }
reed@google.com277c3f82013-05-31 15:17:50 +0000884 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000885 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000886 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000887 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000888 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000889 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 }
891 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000892
893 // signal that we need a moveTo to follow us (unless we're done)
894#if 0
895 if (fLastMoveToIndex >= 0) {
896 fLastMoveToIndex = ~fLastMoveToIndex;
897 }
898#else
899 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
900#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901}
902
903///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000904
fmalitac08d53e2015-11-17 09:53:29 -0800905namespace {
906
907template <unsigned N>
908class PointIterator {
909public:
910 PointIterator(SkPath::Direction dir, unsigned startIndex)
911 : fCurrent(startIndex % N)
912 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
913
914 const SkPoint& current() const {
915 SkASSERT(fCurrent < N);
916 return fPts[fCurrent];
917 }
918
919 const SkPoint& next() {
920 fCurrent = (fCurrent + fAdvance) % N;
921 return this->current();
922 }
923
924protected:
925 SkPoint fPts[N];
926
927private:
928 unsigned fCurrent;
929 unsigned fAdvance;
930};
931
932class RectPointIterator : public PointIterator<4> {
933public:
934 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
935 : PointIterator(dir, startIndex) {
936
937 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
938 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
939 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
940 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
941 }
942};
943
944class OvalPointIterator : public PointIterator<4> {
945public:
946 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
947 : PointIterator(dir, startIndex) {
948
949 const SkScalar cx = oval.centerX();
950 const SkScalar cy = oval.centerY();
951
952 fPts[0] = SkPoint::Make(cx, oval.fTop);
953 fPts[1] = SkPoint::Make(oval.fRight, cy);
954 fPts[2] = SkPoint::Make(cx, oval.fBottom);
955 fPts[3] = SkPoint::Make(oval.fLeft, cy);
956 }
957};
958
959class RRectPointIterator : public PointIterator<8> {
960public:
961 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
962 : PointIterator(dir, startIndex) {
963
964 const SkRect& bounds = rrect.getBounds();
965 const SkScalar L = bounds.fLeft;
966 const SkScalar T = bounds.fTop;
967 const SkScalar R = bounds.fRight;
968 const SkScalar B = bounds.fBottom;
969
970 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
971 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
972 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
973 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
974 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
975 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
976 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
977 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
978 }
979};
980
981} // anonymous namespace
982
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000983static void assert_known_direction(int dir) {
984 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
985}
986
reed@android.com8a1c16f2008-12-17 15:59:43 +0000987void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800988 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989}
990
991void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
992 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800993 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
994}
995
996void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000997 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700998 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800999 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001000 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001001 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001002
fmalitac08d53e2015-11-17 09:53:29 -08001003 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004
fmalitac08d53e2015-11-17 09:53:29 -08001005 const int kVerbs = 5; // moveTo + 3x lineTo + close
1006 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007
fmalitac08d53e2015-11-17 09:53:29 -08001008 RectPointIterator iter(rect, dir, startIndex);
1009
1010 this->moveTo(iter.current());
1011 this->lineTo(iter.next());
1012 this->lineTo(iter.next());
1013 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001015
1016 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017}
1018
reed@google.com744faba2012-05-29 19:54:52 +00001019void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1020 SkDEBUGCODE(this->validate();)
1021 if (count <= 0) {
1022 return;
1023 }
1024
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001025 fLastMoveToIndex = fPathRef->countPoints();
1026
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001027 // +close makes room for the extra kClose_Verb
1028 SkPathRef::Editor ed(&fPathRef, count+close, count);
1029
1030 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001031 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1033 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001034 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001035
reed@google.com744faba2012-05-29 19:54:52 +00001036 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001037 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001038 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001039 }
1040
reed@google.com744faba2012-05-29 19:54:52 +00001041 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001042 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001043}
1044
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045#include "SkGeometry.h"
1046
reedf90ea012015-01-29 12:03:58 -08001047static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1048 SkPoint* pt) {
1049 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050 // Chrome uses this path to move into and out of ovals. If not
1051 // treated as a special case the moves can distort the oval's
1052 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001053 pt->set(oval.fRight, oval.centerY());
1054 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055 } else if (0 == oval.width() && 0 == oval.height()) {
1056 // Chrome will sometimes create 0 radius round rects. Having degenerate
1057 // quad segments in the path prevents the path from being recognized as
1058 // a rect.
1059 // TODO: optimizing the case where only one of width or height is zero
1060 // should also be considered. This case, however, doesn't seem to be
1061 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001062 pt->set(oval.fRight, oval.fTop);
1063 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064 }
reedf90ea012015-01-29 12:03:58 -08001065 return false;
1066}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001067
reedd5d27d92015-02-09 13:54:43 -08001068// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1069//
1070static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1071 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1072 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1073 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001074
1075 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001076 loss in radians-conversion and/or sin/cos, we may end up with coincident
1077 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1078 of drawing a nearly complete circle (good).
1079 e.g. canvas.drawArc(0, 359.99, ...)
1080 -vs- canvas.drawArc(0, 359.9, ...)
1081 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001082 */
reedd5d27d92015-02-09 13:54:43 -08001083 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084 SkScalar sw = SkScalarAbs(sweepAngle);
1085 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1086 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1087 // make a guess at a tiny angle (in radians) to tweak by
1088 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1089 // not sure how much will be enough, so we use a loop
1090 do {
1091 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001092 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1093 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001094 }
1095 }
reedd5d27d92015-02-09 13:54:43 -08001096 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1097}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001098
reed9e779d42015-02-17 11:43:14 -08001099/**
1100 * If this returns 0, then the caller should just line-to the singlePt, else it should
1101 * ignore singlePt and append the specified number of conics.
1102 */
reedd5d27d92015-02-09 13:54:43 -08001103static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001104 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1105 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001106 SkMatrix matrix;
1107
1108 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1109 matrix.postTranslate(oval.centerX(), oval.centerY());
1110
reed9e779d42015-02-17 11:43:14 -08001111 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1112 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001113 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001114 }
1115 return count;
reedd5d27d92015-02-09 13:54:43 -08001116}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001117
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001118void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001120 SkRRect rrect;
1121 rrect.setRectRadii(rect, (const SkVector*) radii);
1122 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123}
1124
reed@google.com4ed0fb72012-12-12 20:48:18 +00001125void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001126 // legacy start indices: 6 (CW) and 7(CCW)
1127 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1128}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001129
fmalitac08d53e2015-11-17 09:53:29 -08001130void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1131 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001132
caryclarkda707bf2015-11-19 14:47:43 -08001133 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001134 const SkRect& bounds = rrect.getBounds();
1135
Brian Salomon0a241ce2017-12-15 11:31:05 -05001136 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001137 // degenerate(rect) => radii points are collapsing
1138 this->addRect(bounds, dir, (startIndex + 1) / 2);
1139 } else if (rrect.isOval()) {
1140 // degenerate(oval) => line points are collapsing
1141 this->addOval(bounds, dir, startIndex / 2);
1142 } else {
1143 fFirstDirection = this->hasOnlyMoveTos() ?
1144 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1145
1146 SkAutoPathBoundsUpdate apbu(this, bounds);
1147 SkAutoDisableDirectionCheck addc(this);
1148
1149 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1150 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1151 const SkScalar weight = SK_ScalarRoot2Over2;
1152
1153 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1154 const int kVerbs = startsWithConic
1155 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1156 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1157 this->incReserve(kVerbs);
1158
1159 RRectPointIterator rrectIter(rrect, dir, startIndex);
1160 // Corner iterator indices follow the collapsed radii model,
1161 // adjusted such that the start pt is "behind" the radii start pt.
1162 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1163 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1164
1165 this->moveTo(rrectIter.current());
1166 if (startsWithConic) {
1167 for (unsigned i = 0; i < 3; ++i) {
1168 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1169 this->lineTo(rrectIter.next());
1170 }
1171 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1172 // final lineTo handled by close().
1173 } else {
1174 for (unsigned i = 0; i < 4; ++i) {
1175 this->lineTo(rrectIter.next());
1176 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1177 }
1178 }
1179 this->close();
1180
caryclarkda707bf2015-11-19 14:47:43 -08001181 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001182 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001183
fmalitac08d53e2015-11-17 09:53:29 -08001184 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1185 }
1186
1187 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001188}
1189
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001190bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001191 int count = fPathRef->countVerbs();
1192 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1193 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194 if (*verbs == kLine_Verb ||
1195 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001196 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197 *verbs == kCubic_Verb) {
1198 return false;
1199 }
1200 ++verbs;
1201 }
1202 return true;
1203}
1204
Brian Osmana2318572017-07-10 15:09:26 -04001205bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1206 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001207 if (count < 2) {
1208 return true;
1209 }
Brian Osmana2318572017-07-10 15:09:26 -04001210 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001211 const SkPoint& first = *pts;
1212 for (int index = 1; index < count; ++index) {
1213 if (first != pts[index]) {
1214 return false;
1215 }
1216 }
1217 return true;
1218}
1219
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001220void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1221 Direction dir) {
1222 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001223
humper@google.com75e3ca12013-04-08 21:44:11 +00001224 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001225 return;
1226 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001227
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001228 SkRRect rrect;
1229 rrect.setRectXY(rect, rx, ry);
1230 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001231}
1232
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001234 // legacy start index: 1
1235 this->addOval(oval, dir, 1);
1236}
1237
1238void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001239 assert_known_direction(dir);
1240
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001241 /* If addOval() is called after previous moveTo(),
1242 this path is still marked as an oval. This is used to
1243 fit into WebKit's calling sequences.
1244 We can't simply check isEmpty() in this case, as additional
1245 moveTo() would mark the path non empty.
1246 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001247 bool isOval = hasOnlyMoveTos();
1248 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001249 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001250 } else {
reed026beb52015-06-10 14:23:15 -07001251 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001252 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001253
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001254 SkAutoDisableDirectionCheck addc(this);
Mike Klein8afa5542018-05-22 12:19:13 +00001255 SkAutoPathBoundsUpdate apbu(this, oval);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001256
fmalitac08d53e2015-11-17 09:53:29 -08001257 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1258 const int kVerbs = 6; // moveTo + 4x conicTo + close
1259 this->incReserve(kVerbs);
1260
1261 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1262 // The corner iterator pts are tracking "behind" the oval/radii pts.
1263 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001264 const SkScalar weight = SK_ScalarRoot2Over2;
1265
fmalitac08d53e2015-11-17 09:53:29 -08001266 this->moveTo(ovalIter.current());
1267 for (unsigned i = 0; i < 4; ++i) {
1268 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001269 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271
fmalitac08d53e2015-11-17 09:53:29 -08001272 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1273
robertphillips@google.com466310d2013-12-03 16:43:54 +00001274 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001275
bsalomon78d58d12016-05-27 09:17:04 -07001276 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001277}
1278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1280 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001281 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 }
1283}
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1286 bool forceMoveTo) {
1287 if (oval.width() < 0 || oval.height() < 0) {
1288 return;
1289 }
1290
reedf90ea012015-01-29 12:03:58 -08001291 if (fPathRef->countVerbs() == 0) {
1292 forceMoveTo = true;
1293 }
1294
1295 SkPoint lonePt;
1296 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1297 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1298 return;
1299 }
1300
reedd5d27d92015-02-09 13:54:43 -08001301 SkVector startV, stopV;
1302 SkRotationDirection dir;
1303 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1304
reed9e779d42015-02-17 11:43:14 -08001305 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001306
Brian Salomone4949402018-04-26 15:22:04 -04001307 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
1308 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
1309 // arcs from the same oval.
1310 auto addPt = [&forceMoveTo, this](const SkPoint& pt) {
1311 SkPoint lastPt;
Brian Salomone4949402018-04-26 15:22:04 -04001312 if (forceMoveTo) {
1313 this->moveTo(pt);
Brian Salomon91840ab2018-05-04 14:11:40 -04001314 } else if (!this->getLastPt(&lastPt) ||
Brian Salomone4949402018-04-26 15:22:04 -04001315 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1316 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1317 this->lineTo(pt);
1318 }
1319 };
1320
xidachen6069dda2016-10-06 05:42:23 -07001321 // At this point, we know that the arc is not a lone point, but startV == stopV
1322 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1323 // cannot handle it.
1324 if (startV == stopV) {
1325 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1326 SkScalar radiusX = oval.width() / 2;
1327 SkScalar radiusY = oval.height() / 2;
1328 // We cannot use SkScalarSinCos function in the next line because
1329 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1330 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001331 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001332 // make sin(endAngle) to be 0 which will then draw a dot.
1333 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1334 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomone4949402018-04-26 15:22:04 -04001335 addPt(singlePt);
xidachen6069dda2016-10-06 05:42:23 -07001336 return;
1337 }
1338
reedd5d27d92015-02-09 13:54:43 -08001339 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001340 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001341 if (count) {
1342 this->incReserve(count * 2 + 1);
1343 const SkPoint& pt = conics[0].fPts[0];
Brian Salomone4949402018-04-26 15:22:04 -04001344 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001345 for (int i = 0; i < count; ++i) {
1346 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1347 }
reed9e779d42015-02-17 11:43:14 -08001348 } else {
Brian Salomone4949402018-04-26 15:22:04 -04001349 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001350 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001351}
1352
caryclark55d49052016-01-23 05:07:04 -08001353// This converts the SVG arc to conics.
1354// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1355// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1356// See also SVG implementation notes:
1357// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1358// Note that arcSweep bool value is flipped from the original implementation.
1359void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1360 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001361 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001362 SkPoint srcPts[2];
1363 this->getLastPt(&srcPts[0]);
1364 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1365 // joining the endpoints.
1366 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1367 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001368 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001369 return;
1370 }
1371 // If the current point and target point for the arc are identical, it should be treated as a
1372 // zero length path. This ensures continuity in animations.
1373 srcPts[1].set(x, y);
1374 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001375 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001376 return;
1377 }
1378 rx = SkScalarAbs(rx);
1379 ry = SkScalarAbs(ry);
1380 SkVector midPointDistance = srcPts[0] - srcPts[1];
1381 midPointDistance *= 0.5f;
1382
1383 SkMatrix pointTransform;
1384 pointTransform.setRotate(-angle);
1385
1386 SkPoint transformedMidPoint;
1387 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1388 SkScalar squareRx = rx * rx;
1389 SkScalar squareRy = ry * ry;
1390 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1391 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1392
1393 // Check if the radii are big enough to draw the arc, scale radii if not.
1394 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1395 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1396 if (radiiScale > 1) {
1397 radiiScale = SkScalarSqrt(radiiScale);
1398 rx *= radiiScale;
1399 ry *= radiiScale;
1400 }
1401
1402 pointTransform.setScale(1 / rx, 1 / ry);
1403 pointTransform.preRotate(-angle);
1404
1405 SkPoint unitPts[2];
1406 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1407 SkVector delta = unitPts[1] - unitPts[0];
1408
1409 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1410 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1411
1412 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1413 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1414 scaleFactor = -scaleFactor;
1415 }
1416 delta.scale(scaleFactor);
1417 SkPoint centerPoint = unitPts[0] + unitPts[1];
1418 centerPoint *= 0.5f;
1419 centerPoint.offset(-delta.fY, delta.fX);
1420 unitPts[0] -= centerPoint;
1421 unitPts[1] -= centerPoint;
1422 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1423 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1424 SkScalar thetaArc = theta2 - theta1;
1425 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1426 thetaArc += SK_ScalarPI * 2;
1427 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1428 thetaArc -= SK_ScalarPI * 2;
1429 }
1430 pointTransform.setRotate(angle);
1431 pointTransform.preScale(rx, ry);
1432
Cary Clark1acd3c72017-12-08 11:37:01 -05001433#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001434 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001435#else
1436 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1437 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1438#endif
caryclark55d49052016-01-23 05:07:04 -08001439 SkScalar thetaWidth = thetaArc / segments;
1440 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1441 if (!SkScalarIsFinite(t)) {
1442 return;
1443 }
1444 SkScalar startTheta = theta1;
1445 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001446#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1447 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1448 return scalar == SkScalarFloorToScalar(scalar);
1449 };
1450 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1451 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1452 scalar_is_integer(x) && scalar_is_integer(y);
1453#endif
caryclark55d49052016-01-23 05:07:04 -08001454 for (int i = 0; i < segments; ++i) {
1455 SkScalar endTheta = startTheta + thetaWidth;
1456 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1457
1458 unitPts[1].set(cosEndTheta, sinEndTheta);
1459 unitPts[1] += centerPoint;
1460 unitPts[0] = unitPts[1];
1461 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1462 SkPoint mapped[2];
1463 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001464 /*
1465 Computing the arc width introduces rounding errors that cause arcs to start
1466 outside their marks. A round rect may lose convexity as a result. If the input
1467 values are on integers, place the conic on integers as well.
1468 */
1469#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1470 if (expectIntegers) {
1471 SkScalar* mappedScalars = &mapped[0].fX;
1472 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1473 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1474 }
1475 }
1476#endif
caryclark55d49052016-01-23 05:07:04 -08001477 this->conicTo(mapped[0], mapped[1], w);
1478 startTheta = endTheta;
1479 }
1480}
1481
1482void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1483 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1484 SkPoint currentPoint;
1485 this->getLastPt(&currentPoint);
1486 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1487}
1488
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001489void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 if (oval.isEmpty() || 0 == sweepAngle) {
1491 return;
1492 }
1493
1494 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1495
1496 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001497 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1498 // See SkPath::addOval() docs.
1499 SkScalar startOver90 = startAngle / 90.f;
1500 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1501 SkScalar error = startOver90 - startOver90I;
1502 if (SkScalarNearlyEqual(error, 0)) {
1503 // Index 1 is at startAngle == 0.
1504 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1505 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1506 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1507 (unsigned) startIndex);
1508 return;
1509 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 }
bsalomon1978ce22016-05-31 10:42:16 -07001511 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512}
1513
1514/*
1515 Need to handle the case when the angle is sharp, and our computed end-points
1516 for the arc go behind pt1 and/or p2...
1517*/
reedc7789042015-01-29 12:59:11 -08001518void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001519 if (radius == 0) {
1520 this->lineTo(x1, y1);
1521 return;
1522 }
1523
1524 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001525
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 // need to know our prev pt so we can construct tangent vectors
1527 {
1528 SkPoint start;
1529 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001530 // Handle degenerate cases by adding a line to the first point and
1531 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 before.setNormalize(x1 - start.fX, y1 - start.fY);
1533 after.setNormalize(x2 - x1, y2 - y1);
1534 }
reed@google.comabf15c12011-01-18 20:35:51 +00001535
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 SkScalar cosh = SkPoint::DotProduct(before, after);
1537 SkScalar sinh = SkPoint::CrossProduct(before, after);
1538
1539 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001540 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 return;
1542 }
reed@google.comabf15c12011-01-18 20:35:51 +00001543
Mike Reeda99b6ce2017-02-04 11:04:26 -05001544 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001545
Mike Reeda99b6ce2017-02-04 11:04:26 -05001546 SkScalar xx = x1 - dist * before.fX;
1547 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001548 after.setLength(dist);
1549 this->lineTo(xx, yy);
1550 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1551 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552}
1553
1554///////////////////////////////////////////////////////////////////////////////
1555
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001556void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557 SkMatrix matrix;
1558
1559 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001560 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001561}
1562
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001563void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001564 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001566 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567 SkPoint pts[4];
1568 Verb verb;
1569
Cary Clark9480d822017-10-19 18:01:13 -04001570 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001571 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 while ((verb = iter.next(pts)) != kDone_Verb) {
1573 switch (verb) {
1574 case kMove_Verb:
1575 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001576 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1577 injectMoveToIfNeeded(); // In case last contour is closed
1578 this->lineTo(pts[0]);
1579 } else {
1580 this->moveTo(pts[0]);
1581 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001582 break;
1583 case kLine_Verb:
1584 proc(matrix, &pts[1], &pts[1], 1);
1585 this->lineTo(pts[1]);
1586 break;
1587 case kQuad_Verb:
1588 proc(matrix, &pts[1], &pts[1], 2);
1589 this->quadTo(pts[1], pts[2]);
1590 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001591 case kConic_Verb:
1592 proc(matrix, &pts[1], &pts[1], 2);
1593 this->conicTo(pts[1], pts[2], iter.conicWeight());
1594 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 case kCubic_Verb:
1596 proc(matrix, &pts[1], &pts[1], 3);
1597 this->cubicTo(pts[1], pts[2], pts[3]);
1598 break;
1599 case kClose_Verb:
1600 this->close();
1601 break;
1602 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001603 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001604 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001605 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 }
1607}
1608
1609///////////////////////////////////////////////////////////////////////////////
1610
reed@google.com277c3f82013-05-31 15:17:50 +00001611static int pts_in_verb(unsigned verb) {
1612 static const uint8_t gPtsInVerb[] = {
1613 1, // kMove
1614 1, // kLine
1615 2, // kQuad
1616 2, // kConic
1617 3, // kCubic
1618 0, // kClose
1619 0 // kDone
1620 };
1621
1622 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1623 return gPtsInVerb[verb];
1624}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001625
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626// ignore the last point of the 1st contour
1627void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001628 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1629 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630 return;
1631 }
caryclark51c56782016-11-07 05:09:28 -08001632 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1633 SkASSERT(verbsEnd[0] == kMove_Verb);
1634 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1635 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636
caryclark51c56782016-11-07 05:09:28 -08001637 while (verbs < verbsEnd) {
1638 uint8_t v = *verbs++;
1639 pts -= pts_in_verb(v);
1640 switch (v) {
1641 case kMove_Verb:
1642 // if the path has multiple contours, stop after reversing the last
1643 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001645 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 break;
1647 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001648 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001650 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001651 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001652 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001654 this->cubicTo(pts[2], pts[1], pts[0]);
1655 break;
1656 case kClose_Verb:
1657 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 break;
1659 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001660 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001661 break;
1662 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 }
1664}
1665
reed@google.com63d73742012-01-10 15:33:12 +00001666void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001667 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001668
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001669 const SkPoint* pts = src.fPathRef->pointsEnd();
1670 // we will iterator through src's verbs backwards
1671 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1672 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001673 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001674
1675 bool needMove = true;
1676 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 while (verbs < verbsEnd) {
1678 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001679 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001680
1681 if (needMove) {
1682 --pts;
1683 this->moveTo(pts->fX, pts->fY);
1684 needMove = false;
1685 }
1686 pts -= n;
1687 switch (v) {
1688 case kMove_Verb:
1689 if (needClose) {
1690 this->close();
1691 needClose = false;
1692 }
1693 needMove = true;
1694 pts += 1; // so we see the point in "if (needMove)" above
1695 break;
1696 case kLine_Verb:
1697 this->lineTo(pts[0]);
1698 break;
1699 case kQuad_Verb:
1700 this->quadTo(pts[1], pts[0]);
1701 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001702 case kConic_Verb:
1703 this->conicTo(pts[1], pts[0], *--conicWeights);
1704 break;
reed@google.com63d73742012-01-10 15:33:12 +00001705 case kCubic_Verb:
1706 this->cubicTo(pts[2], pts[1], pts[0]);
1707 break;
1708 case kClose_Verb:
1709 needClose = true;
1710 break;
1711 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001712 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001713 }
1714 }
1715}
1716
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717///////////////////////////////////////////////////////////////////////////////
1718
1719void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1720 SkMatrix matrix;
1721
1722 matrix.setTranslate(dx, dy);
1723 this->transform(matrix, dst);
1724}
1725
reed@android.com8a1c16f2008-12-17 15:59:43 +00001726static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1727 int level = 2) {
1728 if (--level >= 0) {
1729 SkPoint tmp[7];
1730
1731 SkChopCubicAtHalf(pts, tmp);
1732 subdivide_cubic_to(path, &tmp[0], level);
1733 subdivide_cubic_to(path, &tmp[3], level);
1734 } else {
1735 path->cubicTo(pts[1], pts[2], pts[3]);
1736 }
1737}
1738
1739void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1740 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001741 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742 dst = (SkPath*)this;
1743 }
1744
tomhudson@google.com8d430182011-06-06 19:11:19 +00001745 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 SkPath tmp;
1747 tmp.fFillType = fFillType;
1748
1749 SkPath::Iter iter(*this, false);
1750 SkPoint pts[4];
1751 SkPath::Verb verb;
1752
reed@google.com4a3b7142012-05-16 17:16:46 +00001753 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001754 switch (verb) {
1755 case kMove_Verb:
1756 tmp.moveTo(pts[0]);
1757 break;
1758 case kLine_Verb:
1759 tmp.lineTo(pts[1]);
1760 break;
1761 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001762 // promote the quad to a conic
1763 tmp.conicTo(pts[1], pts[2],
1764 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001765 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001766 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001767 tmp.conicTo(pts[1], pts[2],
1768 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001769 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 case kCubic_Verb:
1771 subdivide_cubic_to(&tmp, pts);
1772 break;
1773 case kClose_Verb:
1774 tmp.close();
1775 break;
1776 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001777 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 break;
1779 }
1780 }
1781
1782 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001783 SkPathRef::Editor ed(&dst->fPathRef);
1784 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001785 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001787 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1788
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001791 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001792 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001794
reed026beb52015-06-10 14:23:15 -07001795 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1796 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001797 } else {
1798 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001799 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1800 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001801 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001802 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1803 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001804 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001805 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001806 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001807 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001808 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001809 }
1810 }
1811
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 SkDEBUGCODE(dst->validate();)
1813 }
1814}
1815
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816///////////////////////////////////////////////////////////////////////////////
1817///////////////////////////////////////////////////////////////////////////////
1818
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001820 kEmptyContour_SegmentState, // The current contour is empty. We may be
1821 // starting processing or we may have just
1822 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1824 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1825 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826};
1827
1828SkPath::Iter::Iter() {
1829#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001830 fPts = nullptr;
1831 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001832 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001834 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835#endif
1836 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001837 fVerbs = nullptr;
1838 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 fNeedClose = false;
1840}
1841
1842SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1843 this->setPath(path, forceClose);
1844}
1845
1846void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001847 fPts = path.fPathRef->points();
1848 fVerbs = path.fPathRef->verbs();
1849 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001850 fConicWeights = path.fPathRef->conicWeights();
1851 if (fConicWeights) {
1852 fConicWeights -= 1; // begin one behind
1853 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001855 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 fForceClose = SkToU8(forceClose);
1857 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001858 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001859}
1860
1861bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001862 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001863 return false;
1864 }
1865 if (fForceClose) {
1866 return true;
1867 }
1868
1869 const uint8_t* verbs = fVerbs;
1870 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001871
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001872 if (kMove_Verb == *(verbs - 1)) {
1873 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 }
1875
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001876 while (verbs > stop) {
1877 // verbs points one beyond the current verb, decrement first.
1878 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 if (kMove_Verb == v) {
1880 break;
1881 }
1882 if (kClose_Verb == v) {
1883 return true;
1884 }
1885 }
1886 return false;
1887}
1888
1889SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001890 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001892 // A special case: if both points are NaN, SkPoint::operation== returns
1893 // false, but the iterator expects that they are treated as the same.
1894 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001895 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1896 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001897 return kClose_Verb;
1898 }
1899
reed@google.com9e25dbf2012-05-15 17:05:38 +00001900 pts[0] = fLastPt;
1901 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 fLastPt = fMoveTo;
1903 fCloseLine = true;
1904 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001905 } else {
1906 pts[0] = fMoveTo;
1907 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909}
1910
reed@google.com9e25dbf2012-05-15 17:05:38 +00001911const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 if (fSegmentState == kAfterMove_SegmentState) {
1913 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1918 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001919 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921}
1922
caryclarke8c56662015-07-14 11:19:26 -07001923void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 // We need to step over anything that will not move the current draw point
1925 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001926 const uint8_t* lastMoveVerb = nullptr;
1927 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001928 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 SkPoint lastPt = fLastPt;
1930 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001931 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 switch (verb) {
1933 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 // Keep a record of this most recent move
1935 lastMoveVerb = fVerbs;
1936 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001937 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001938 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001939 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001940 fPts++;
1941 break;
1942
1943 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001944 // A close when we are in a segment is always valid except when it
1945 // follows a move which follows a segment.
1946 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 return;
1948 }
1949 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 break;
1952
1953 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001954 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001955 if (lastMoveVerb) {
1956 fVerbs = lastMoveVerb;
1957 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001958 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959 return;
1960 }
1961 return;
1962 }
1963 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001964 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001965 fPts++;
1966 break;
1967
reed@google.com277c3f82013-05-31 15:17:50 +00001968 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001970 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 if (lastMoveVerb) {
1972 fVerbs = lastMoveVerb;
1973 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001974 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001975 return;
1976 }
1977 return;
1978 }
1979 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001980 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001981 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001982 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 break;
1984
1985 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001986 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 if (lastMoveVerb) {
1988 fVerbs = lastMoveVerb;
1989 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001990 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991 return;
1992 }
1993 return;
1994 }
1995 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001996 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001997 fPts += 3;
1998 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001999
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002000 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002001 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 }
2003 }
2004}
2005
reed@google.com4a3b7142012-05-16 17:16:46 +00002006SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002007 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002008
reed@android.com8a1c16f2008-12-17 15:59:43 +00002009 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002010 // Close the curve if requested and if there is some curve to close
2011 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002012 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 return kLine_Verb;
2014 }
2015 fNeedClose = false;
2016 return kClose_Verb;
2017 }
2018 return kDone_Verb;
2019 }
2020
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002021 // fVerbs is one beyond the current verb, decrement first
2022 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002023 const SkPoint* SK_RESTRICT srcPts = fPts;
2024 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025
2026 switch (verb) {
2027 case kMove_Verb:
2028 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002029 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030 verb = this->autoClose(pts);
2031 if (verb == kClose_Verb) {
2032 fNeedClose = false;
2033 }
2034 return (Verb)verb;
2035 }
2036 if (fVerbs == fVerbStop) { // might be a trailing moveto
2037 return kDone_Verb;
2038 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002039 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002040 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002042 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002043 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044 fNeedClose = fForceClose;
2045 break;
2046 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002047 pts[0] = this->cons_moveTo();
2048 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 fLastPt = srcPts[0];
2050 fCloseLine = false;
2051 srcPts += 1;
2052 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002053 case kConic_Verb:
2054 fConicWeights += 1;
2055 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002056 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002057 pts[0] = this->cons_moveTo();
2058 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002059 fLastPt = srcPts[1];
2060 srcPts += 2;
2061 break;
2062 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002063 pts[0] = this->cons_moveTo();
2064 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065 fLastPt = srcPts[2];
2066 srcPts += 3;
2067 break;
2068 case kClose_Verb:
2069 verb = this->autoClose(pts);
2070 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002071 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 } else {
2073 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002074 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002075 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002076 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077 break;
2078 }
2079 fPts = srcPts;
2080 return (Verb)verb;
2081}
2082
2083///////////////////////////////////////////////////////////////////////////////
2084
Ben Wagner4d1955c2017-03-10 13:08:15 -05002085#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002086#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002087#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002088
reed@google.com51bbe752013-01-17 22:07:50 +00002089static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002090 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002091 str->append(label);
2092 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002093
reed@google.com51bbe752013-01-17 22:07:50 +00002094 const SkScalar* values = &pts[0].fX;
2095 count *= 2;
2096
2097 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002098 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002099 if (i < count - 1) {
2100 str->append(", ");
2101 }
2102 }
Mike Reed405b9d22018-01-09 15:11:08 -05002103 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002104 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002105 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002106 }
caryclark08fa28c2014-10-23 13:08:56 -07002107 str->append(");");
reede05fed02014-12-15 07:59:53 -08002108 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002109 str->append(" // ");
2110 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002111 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002112 if (i < count - 1) {
2113 str->append(", ");
2114 }
2115 }
2116 if (conicWeight >= 0) {
2117 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002118 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002119 }
2120 }
2121 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002122}
2123
caryclarke9562592014-09-15 09:26:09 -07002124void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002125 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 Iter iter(*this, forceClose);
2127 SkPoint pts[4];
2128 Verb verb;
2129
reed@google.com51bbe752013-01-17 22:07:50 +00002130 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002131 char const * const gFillTypeStrs[] = {
2132 "Winding",
2133 "EvenOdd",
2134 "InverseWinding",
2135 "InverseEvenOdd",
2136 };
2137 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2138 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002139 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140 switch (verb) {
2141 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002142 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 break;
2144 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002145 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 break;
2147 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002148 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002150 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002151 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002152 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002153 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002154 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155 break;
2156 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002157 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 break;
2159 default:
2160 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2161 verb = kDone_Verb; // stop the loop
2162 break;
2163 }
caryclark1049f122015-04-20 08:31:59 -07002164 if (!wStream && builder.size()) {
2165 SkDebugf("%s", builder.c_str());
2166 builder.reset();
2167 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 }
caryclark66a5d8b2014-06-24 08:30:15 -07002169 if (wStream) {
2170 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002171 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172}
2173
reed@android.come522ca52009-11-23 20:10:41 +00002174void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002175 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002176}
2177
2178void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002179 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002180}
2181
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002182
Cary Clark0461e9f2017-08-25 15:13:38 -04002183bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002184 if ((fFillType & ~3) != 0) {
2185 return false;
2186 }
reed@google.comabf15c12011-01-18 20:35:51 +00002187
djsollen@google.com077348c2012-10-22 20:23:32 +00002188#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002189 if (!fBoundsIsDirty) {
2190 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002191
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002192 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002193 if (SkToBool(fIsFinite) != isFinite) {
2194 return false;
2195 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002196
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002197 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002198 // if we're empty, fBounds may be empty but translated, so we can't
2199 // necessarily compare to bounds directly
2200 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2201 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002202 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2203 return false;
2204 }
reed@android.come522ca52009-11-23 20:10:41 +00002205 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002206 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002207 if (!fBounds.isEmpty()) {
2208 return false;
2209 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002210 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002211 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002212 if (!fBounds.contains(bounds)) {
2213 return false;
2214 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002215 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002216 }
reed@android.come522ca52009-11-23 20:10:41 +00002217 }
2218 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002219#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002220 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002221}
reed@android.come522ca52009-11-23 20:10:41 +00002222
reed@google.com04863fa2011-05-15 04:08:24 +00002223///////////////////////////////////////////////////////////////////////////////
2224
reed@google.com0b7b9822011-05-16 12:29:27 +00002225static int sign(SkScalar x) { return x < 0; }
2226#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002227
robertphillipsc506e302014-09-16 09:43:31 -07002228enum DirChange {
2229 kLeft_DirChange,
2230 kRight_DirChange,
2231 kStraight_DirChange,
2232 kBackwards_DirChange,
2233
2234 kInvalid_DirChange
2235};
2236
2237
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002238static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002239 // The error epsilon was empirically derived; worse case round rects
2240 // with a mid point outset by 2x float epsilon in tests had an error
2241 // of 12.
2242 const int epsilon = 16;
2243 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2244 return false;
2245 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002246 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002247 int aBits = SkFloatAs2sCompliment(compA);
2248 int bBits = SkFloatAs2sCompliment(compB);
2249 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002250}
2251
caryclarkb4216502015-03-02 13:02:34 -08002252static bool approximately_zero_when_compared_to(double x, double y) {
2253 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002254}
2255
caryclarkb4216502015-03-02 13:02:34 -08002256
reed@google.com04863fa2011-05-15 04:08:24 +00002257// only valid for a single contour
2258struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002259 Convexicator()
2260 : fPtCount(0)
2261 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002262 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002263 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002264 , fIsCurve(false)
2265 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002266 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002267 // warnings
djsollen2f124632016-04-29 13:53:05 -07002268 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002269 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002270 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002271 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002272 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002273
2274 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002275 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002276 }
2277
2278 SkPath::Convexity getConvexity() const { return fConvexity; }
2279
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002280 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002281 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282
reed@google.com04863fa2011-05-15 04:08:24 +00002283 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002284 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002285 return;
2286 }
2287
2288 if (0 == fPtCount) {
2289 fCurrPt = pt;
2290 ++fPtCount;
2291 } else {
2292 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002293 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002294 if (!SkScalarIsFinite(lengthSqd)) {
2295 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002296 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002297 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002298 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 fCurrPt = pt;
2300 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002301 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002302 } else {
2303 SkASSERT(fPtCount > 2);
2304 this->addVec(vec);
2305 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002306
reed@google.com85b6e392011-05-15 20:25:17 +00002307 int sx = sign(vec.fX);
2308 int sy = sign(vec.fY);
2309 fDx += (sx != fSx);
2310 fDy += (sy != fSy);
2311 fSx = sx;
2312 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002313
reed@google.com85b6e392011-05-15 20:25:17 +00002314 if (fDx > 3 || fDy > 3) {
2315 fConvexity = SkPath::kConcave_Convexity;
2316 }
reed@google.com04863fa2011-05-15 04:08:24 +00002317 }
2318 }
2319 }
2320
2321 void close() {
2322 if (fPtCount > 2) {
2323 this->addVec(fFirstVec);
2324 }
2325 }
2326
caryclarkb4216502015-03-02 13:02:34 -08002327 DirChange directionChange(const SkVector& curVec) {
2328 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2329
2330 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2331 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2332 largest = SkTMax(largest, -smallest);
2333
2334 if (!almost_equal(largest, largest + cross)) {
2335 int sign = SkScalarSignAsInt(cross);
2336 if (sign) {
2337 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2338 }
2339 }
2340
2341 if (cross) {
2342 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2343 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2344 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2345 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2346 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2347 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2348 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2349 if (sign) {
2350 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2351 }
2352 }
2353 }
2354
Cary Clarkdf429f32017-11-08 11:44:31 -05002355 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2356 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2357 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2358 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002359 fLastVec.dot(curVec) < 0.0f) {
2360 return kBackwards_DirChange;
2361 }
2362
2363 return kStraight_DirChange;
2364 }
2365
Cary Clarkc8323aa2017-08-25 08:04:43 -04002366 bool hasBackwards() const {
2367 return fBackwards;
2368 }
caryclarkb4216502015-03-02 13:02:34 -08002369
caryclarkd3d1a982014-12-08 04:57:38 -08002370 bool isFinite() const {
2371 return fIsFinite;
2372 }
2373
caryclark5ccef572015-03-02 10:07:56 -08002374 void setCurve(bool isCurve) {
2375 fIsCurve = isCurve;
2376 }
2377
reed@google.com04863fa2011-05-15 04:08:24 +00002378private:
2379 void addVec(const SkVector& vec) {
2380 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002381 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002382 switch (dir) {
2383 case kLeft_DirChange: // fall through
2384 case kRight_DirChange:
2385 if (kInvalid_DirChange == fExpectedDir) {
2386 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002387 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2388 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002389 } else if (dir != fExpectedDir) {
2390 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002391 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002392 }
2393 fLastVec = vec;
2394 break;
2395 case kStraight_DirChange:
2396 break;
2397 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002398 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002399 // If any of the subsequent dir is non-backward, it'll be concave.
2400 // Otherwise, it's still convex.
2401 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002402 }
robertphillipsc506e302014-09-16 09:43:31 -07002403 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002404 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002405 break;
2406 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002407 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002408 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002409 }
2410 }
2411
caryclarkb4216502015-03-02 13:02:34 -08002412 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002413 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002414 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002415 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2416 // value with the current vec is deemed to be of a significant value.
2417 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002418 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002419 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002420 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002421 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002422 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002423 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002424 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002425 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002426};
2427
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002428SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002429 // Sometimes we think we need to calculate convexity but another thread already did.
2430 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2431 return c;
2432 }
2433
reed@google.com04863fa2011-05-15 04:08:24 +00002434 SkPoint pts[4];
2435 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002436 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002437
2438 int contourCount = 0;
2439 int count;
2440 Convexicator state;
2441
caryclarkd3d1a982014-12-08 04:57:38 -08002442 if (!isFinite()) {
2443 return kUnknown_Convexity;
2444 }
Brian Osman205a1262017-09-18 13:13:48 +00002445 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002446 switch (verb) {
2447 case kMove_Verb:
2448 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002449 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002450 return kConcave_Convexity;
2451 }
2452 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002453 // fall through
2454 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002455 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002456 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002457 break;
caryclark5ccef572015-03-02 10:07:56 -08002458 case kQuad_Verb:
2459 // fall through
2460 case kConic_Verb:
2461 // fall through
2462 case kCubic_Verb:
2463 count = 2 + (kCubic_Verb == verb);
2464 // As an additional enhancement, this could set curve true only
2465 // if the curve is nonlinear
2466 state.setCurve(true);
2467 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002468 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002469 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002470 state.close();
2471 count = 0;
2472 break;
2473 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002474 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002475 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002476 return kConcave_Convexity;
2477 }
2478
2479 for (int i = 1; i <= count; i++) {
2480 state.addPt(pts[i]);
2481 }
2482 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002483 if (!state.isFinite()) {
2484 return kUnknown_Convexity;
2485 }
reed@google.com04863fa2011-05-15 04:08:24 +00002486 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002487 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002488 return kConcave_Convexity;
2489 }
2490 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002491 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002492 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002493 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2494 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2495 fConvexity = Convexity::kConcave_Convexity;
2496 } else {
2497 fFirstDirection = state.getFirstDirection();
2498 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002499 }
2500 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002501}
reed@google.com69a99432012-01-10 18:00:10 +00002502
2503///////////////////////////////////////////////////////////////////////////////
2504
2505class ContourIter {
2506public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002507 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002508
2509 bool done() const { return fDone; }
2510 // if !done() then these may be called
2511 int count() const { return fCurrPtCount; }
2512 const SkPoint* pts() const { return fCurrPt; }
2513 void next();
2514
2515private:
2516 int fCurrPtCount;
2517 const SkPoint* fCurrPt;
2518 const uint8_t* fCurrVerb;
2519 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002520 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002521 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002522 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002523};
2524
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002525ContourIter::ContourIter(const SkPathRef& pathRef) {
2526 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002527 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002528 fCurrPt = pathRef.points();
2529 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002530 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002531 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002532 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002533 this->next();
2534}
2535
2536void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002537 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002538 fDone = true;
2539 }
2540 if (fDone) {
2541 return;
2542 }
2543
2544 // skip pts of prev contour
2545 fCurrPt += fCurrPtCount;
2546
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002547 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002548 int ptCount = 1; // moveTo
2549 const uint8_t* verbs = fCurrVerb;
2550
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002551 for (--verbs; verbs > fStopVerbs; --verbs) {
2552 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002553 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002554 goto CONTOUR_END;
2555 case SkPath::kLine_Verb:
2556 ptCount += 1;
2557 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002558 case SkPath::kConic_Verb:
2559 fCurrConicWeight += 1;
2560 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002561 case SkPath::kQuad_Verb:
2562 ptCount += 2;
2563 break;
2564 case SkPath::kCubic_Verb:
2565 ptCount += 3;
2566 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002567 case SkPath::kClose_Verb:
2568 break;
2569 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002570 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002571 break;
2572 }
2573 }
2574CONTOUR_END:
2575 fCurrPtCount = ptCount;
2576 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002577 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002578}
2579
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002580// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002581static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002582 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2583 // We may get 0 when the above subtracts underflow. We expect this to be
2584 // very rare and lazily promote to double.
2585 if (0 == cross) {
2586 double p0x = SkScalarToDouble(p0.fX);
2587 double p0y = SkScalarToDouble(p0.fY);
2588
2589 double p1x = SkScalarToDouble(p1.fX);
2590 double p1y = SkScalarToDouble(p1.fY);
2591
2592 double p2x = SkScalarToDouble(p2.fX);
2593 double p2y = SkScalarToDouble(p2.fY);
2594
2595 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2596 (p1y - p0y) * (p2x - p0x));
2597
2598 }
2599 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002600}
2601
reed@google.comc1ea60a2012-01-31 15:15:36 +00002602// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002603static int find_max_y(const SkPoint pts[], int count) {
2604 SkASSERT(count > 0);
2605 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002606 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002607 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002608 SkScalar y = pts[i].fY;
2609 if (y > max) {
2610 max = y;
2611 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002612 }
2613 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002614 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002615}
2616
reed@google.comcabaf1d2012-01-11 21:03:05 +00002617static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2618 int i = index;
2619 for (;;) {
2620 i = (i + inc) % n;
2621 if (i == index) { // we wrapped around, so abort
2622 break;
2623 }
2624 if (pts[index] != pts[i]) { // found a different point, success!
2625 break;
2626 }
2627 }
2628 return i;
2629}
2630
reed@google.comc1ea60a2012-01-31 15:15:36 +00002631/**
2632 * Starting at index, and moving forward (incrementing), find the xmin and
2633 * xmax of the contiguous points that have the same Y.
2634 */
2635static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2636 int* maxIndexPtr) {
2637 const SkScalar y = pts[index].fY;
2638 SkScalar min = pts[index].fX;
2639 SkScalar max = min;
2640 int minIndex = index;
2641 int maxIndex = index;
2642 for (int i = index + 1; i < n; ++i) {
2643 if (pts[i].fY != y) {
2644 break;
2645 }
2646 SkScalar x = pts[i].fX;
2647 if (x < min) {
2648 min = x;
2649 minIndex = i;
2650 } else if (x > max) {
2651 max = x;
2652 maxIndex = i;
2653 }
2654 }
2655 *maxIndexPtr = maxIndex;
2656 return minIndex;
2657}
2658
reed026beb52015-06-10 14:23:15 -07002659static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2660 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002661}
2662
reed@google.comac8543f2012-01-30 20:51:25 +00002663/*
2664 * We loop through all contours, and keep the computed cross-product of the
2665 * contour that contained the global y-max. If we just look at the first
2666 * contour, we may find one that is wound the opposite way (correctly) since
2667 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2668 * that is outer most (or at least has the global y-max) before we can consider
2669 * its cross product.
2670 */
reed026beb52015-06-10 14:23:15 -07002671bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002672 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2673 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002674 return true;
2675 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002676
2677 // don't want to pay the cost for computing this if it
2678 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002679 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2680 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002681 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002682 return false;
2683 }
reed@google.com69a99432012-01-10 18:00:10 +00002684
reed026beb52015-06-10 14:23:15 -07002685 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002686
reed@google.comac8543f2012-01-30 20:51:25 +00002687 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002688 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002689 SkScalar ymaxCross = 0;
2690
reed@google.com69a99432012-01-10 18:00:10 +00002691 for (; !iter.done(); iter.next()) {
2692 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002693 if (n < 3) {
2694 continue;
2695 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002696
reed@google.comcabaf1d2012-01-11 21:03:05 +00002697 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002698 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002699 int index = find_max_y(pts, n);
2700 if (pts[index].fY < ymax) {
2701 continue;
2702 }
2703
2704 // If there is more than 1 distinct point at the y-max, we take the
2705 // x-min and x-max of them and just subtract to compute the dir.
2706 if (pts[(index + 1) % n].fY == pts[index].fY) {
2707 int maxIndex;
2708 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2709 if (minIndex == maxIndex) {
2710 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002711 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002712 SkASSERT(pts[minIndex].fY == pts[index].fY);
2713 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2714 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2715 // we just subtract the indices, and let that auto-convert to
2716 // SkScalar, since we just want - or + to signal the direction.
2717 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002718 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002719 TRY_CROSSPROD:
2720 // Find a next and prev index to use for the cross-product test,
2721 // but we try to find pts that form non-zero vectors from pts[index]
2722 //
2723 // Its possible that we can't find two non-degenerate vectors, so
2724 // we have to guard our search (e.g. all the pts could be in the
2725 // same place).
2726
2727 // we pass n - 1 instead of -1 so we don't foul up % operator by
2728 // passing it a negative LH argument.
2729 int prev = find_diff_pt(pts, index, n, n - 1);
2730 if (prev == index) {
2731 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002732 continue;
2733 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002734 int next = find_diff_pt(pts, index, n, 1);
2735 SkASSERT(next != index);
2736 cross = cross_prod(pts[prev], pts[index], pts[next]);
2737 // if we get a zero and the points are horizontal, then we look at the spread in
2738 // x-direction. We really should continue to walk away from the degeneracy until
2739 // there is a divergence.
2740 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2741 // construct the subtract so we get the correct Direction below
2742 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002743 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002744 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002745
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002746 if (cross) {
2747 // record our best guess so far
2748 ymax = pts[index].fY;
2749 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002750 }
2751 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002752 if (ymaxCross) {
2753 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002754 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002755 return true;
2756 } else {
2757 return false;
2758 }
reed@google.comac8543f2012-01-30 20:51:25 +00002759}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760
2761///////////////////////////////////////////////////////////////////////////////
2762
caryclark9aacd902015-12-14 08:38:09 -08002763static bool between(SkScalar a, SkScalar b, SkScalar c) {
2764 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2765 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2766 return (a - b) * (c - b) <= 0;
2767}
2768
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002769static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2770 SkScalar t) {
2771 SkScalar A = c3 + 3*(c1 - c2) - c0;
2772 SkScalar B = 3*(c2 - c1 - c1 + c0);
2773 SkScalar C = 3*(c1 - c0);
2774 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002775 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002776}
2777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778template <size_t N> static void find_minmax(const SkPoint pts[],
2779 SkScalar* minPtr, SkScalar* maxPtr) {
2780 SkScalar min, max;
2781 min = max = pts[0].fX;
2782 for (size_t i = 1; i < N; ++i) {
2783 min = SkMinScalar(min, pts[i].fX);
2784 max = SkMaxScalar(max, pts[i].fX);
2785 }
2786 *minPtr = min;
2787 *maxPtr = max;
2788}
2789
caryclark9cb5d752015-12-18 04:35:24 -08002790static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2791 if (start.fY == end.fY) {
2792 return between(start.fX, x, end.fX) && x != end.fX;
2793 } else {
2794 return x == start.fX && y == start.fY;
2795 }
2796}
2797
caryclark9aacd902015-12-14 08:38:09 -08002798static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002799 SkScalar y0 = pts[0].fY;
2800 SkScalar y3 = pts[3].fY;
2801
2802 int dir = 1;
2803 if (y0 > y3) {
2804 SkTSwap(y0, y3);
2805 dir = -1;
2806 }
2807 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 return 0;
2809 }
caryclark9cb5d752015-12-18 04:35:24 -08002810 if (checkOnCurve(x, y, pts[0], pts[3])) {
2811 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002812 return 0;
2813 }
caryclark9cb5d752015-12-18 04:35:24 -08002814 if (y == y3) {
2815 return 0;
2816 }
2817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 // quickreject or quickaccept
2819 SkScalar min, max;
2820 find_minmax<4>(pts, &min, &max);
2821 if (x < min) {
2822 return 0;
2823 }
2824 if (x > max) {
2825 return dir;
2826 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002827
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002829 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002830 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002831 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002832 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002833 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002834 if (SkScalarNearlyEqual(xt, x)) {
2835 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2836 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002837 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002838 }
2839 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002840 return xt < x ? dir : 0;
2841}
2842
caryclark9aacd902015-12-14 08:38:09 -08002843static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002844 SkPoint dst[10];
2845 int n = SkChopCubicAtYExtrema(pts, dst);
2846 int w = 0;
2847 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002848 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 }
2850 return w;
2851}
2852
caryclark9aacd902015-12-14 08:38:09 -08002853static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2854 SkASSERT(src);
2855 SkASSERT(t >= 0 && t <= 1);
2856 SkScalar src2w = src[2] * w;
2857 SkScalar C = src[0];
2858 SkScalar A = src[4] - 2 * src2w + C;
2859 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002860 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002861}
2862
2863
2864static double conic_eval_denominator(SkScalar w, SkScalar t) {
2865 SkScalar B = 2 * (w - 1);
2866 SkScalar C = 1;
2867 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002868 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002869}
2870
2871static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2872 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 SkScalar y0 = pts[0].fY;
2874 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002875
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002876 int dir = 1;
2877 if (y0 > y2) {
2878 SkTSwap(y0, y2);
2879 dir = -1;
2880 }
caryclark9aacd902015-12-14 08:38:09 -08002881 if (y < y0 || y > y2) {
2882 return 0;
2883 }
caryclark9cb5d752015-12-18 04:35:24 -08002884 if (checkOnCurve(x, y, pts[0], pts[2])) {
2885 *onCurveCount += 1;
2886 return 0;
2887 }
caryclark9aacd902015-12-14 08:38:09 -08002888 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002889 return 0;
2890 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002891
caryclark9aacd902015-12-14 08:38:09 -08002892 SkScalar roots[2];
2893 SkScalar A = pts[2].fY;
2894 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2895 SkScalar C = pts[0].fY;
2896 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2897 B -= C; // B = b*w - w * yCept + yCept - a
2898 C -= y;
2899 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2900 SkASSERT(n <= 1);
2901 SkScalar xt;
2902 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002903 // zero roots are returned only when y0 == y
2904 // Need [0] if dir == 1
2905 // and [2] if dir == -1
2906 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002907 } else {
2908 SkScalar t = roots[0];
2909 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2910 }
2911 if (SkScalarNearlyEqual(xt, x)) {
2912 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2913 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002914 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002915 }
2916 }
2917 return xt < x ? dir : 0;
2918}
2919
2920static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2921 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2922 if (y0 == y1) {
2923 return true;
2924 }
2925 if (y0 < y1) {
2926 return y1 <= y2;
2927 } else {
2928 return y1 >= y2;
2929 }
2930}
2931
2932static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2933 int* onCurveCount) {
2934 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002935 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002936 // If the data points are very large, the conic may not be monotonic but may also
2937 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002938 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2939 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2940 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002941 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2942 }
2943 return w;
2944}
2945
2946static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2947 SkScalar y0 = pts[0].fY;
2948 SkScalar y2 = pts[2].fY;
2949
2950 int dir = 1;
2951 if (y0 > y2) {
2952 SkTSwap(y0, y2);
2953 dir = -1;
2954 }
2955 if (y < y0 || y > y2) {
2956 return 0;
2957 }
caryclark9cb5d752015-12-18 04:35:24 -08002958 if (checkOnCurve(x, y, pts[0], pts[2])) {
2959 *onCurveCount += 1;
2960 return 0;
2961 }
caryclark9aacd902015-12-14 08:38:09 -08002962 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002963 return 0;
2964 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002965 // bounds check on X (not required. is it faster?)
2966#if 0
2967 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2968 return 0;
2969 }
2970#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002971
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 SkScalar roots[2];
2973 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2974 2 * (pts[1].fY - pts[0].fY),
2975 pts[0].fY - y,
2976 roots);
2977 SkASSERT(n <= 1);
2978 SkScalar xt;
2979 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002980 // zero roots are returned only when y0 == y
2981 // Need [0] if dir == 1
2982 // and [2] if dir == -1
2983 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002984 } else {
2985 SkScalar t = roots[0];
2986 SkScalar C = pts[0].fX;
2987 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2988 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002989 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002990 }
caryclark9aacd902015-12-14 08:38:09 -08002991 if (SkScalarNearlyEqual(xt, x)) {
2992 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2993 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002994 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002995 }
2996 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 return xt < x ? dir : 0;
2998}
2999
caryclark9aacd902015-12-14 08:38:09 -08003000static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 SkPoint dst[5];
3002 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003003
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3005 n = SkChopQuadAtYExtrema(pts, dst);
3006 pts = dst;
3007 }
caryclark9aacd902015-12-14 08:38:09 -08003008 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003010 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 }
3012 return w;
3013}
3014
caryclark9aacd902015-12-14 08:38:09 -08003015static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 SkScalar x0 = pts[0].fX;
3017 SkScalar y0 = pts[0].fY;
3018 SkScalar x1 = pts[1].fX;
3019 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003020
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003021 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003022
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003023 int dir = 1;
3024 if (y0 > y1) {
3025 SkTSwap(y0, y1);
3026 dir = -1;
3027 }
caryclark9aacd902015-12-14 08:38:09 -08003028 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003029 return 0;
3030 }
caryclark9cb5d752015-12-18 04:35:24 -08003031 if (checkOnCurve(x, y, pts[0], pts[1])) {
3032 *onCurveCount += 1;
3033 return 0;
3034 }
3035 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003036 return 0;
3037 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003038 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003039
caryclark9aacd902015-12-14 08:38:09 -08003040 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003041 // zero cross means the point is on the line, and since the case where
3042 // y of the query point is at the end point is handled above, we can be
3043 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003044 if (x != x1 || y != pts[1].fY) {
3045 *onCurveCount += 1;
3046 }
caryclark9aacd902015-12-14 08:38:09 -08003047 dir = 0;
3048 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003049 dir = 0;
3050 }
3051 return dir;
3052}
3053
caryclark9aacd902015-12-14 08:38:09 -08003054static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3055 SkTDArray<SkVector>* tangents) {
3056 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3057 && !between(pts[2].fY, y, pts[3].fY)) {
3058 return;
3059 }
3060 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3061 && !between(pts[2].fX, x, pts[3].fX)) {
3062 return;
3063 }
3064 SkPoint dst[10];
3065 int n = SkChopCubicAtYExtrema(pts, dst);
3066 for (int i = 0; i <= n; ++i) {
3067 SkPoint* c = &dst[i * 3];
3068 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003069 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003070 continue;
mbarbella276e6332016-05-31 14:44:01 -07003071 }
caryclark9aacd902015-12-14 08:38:09 -08003072 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3073 if (!SkScalarNearlyEqual(x, xt)) {
3074 continue;
3075 }
3076 SkVector tangent;
3077 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3078 tangents->push(tangent);
3079 }
3080}
3081
3082static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3083 SkTDArray<SkVector>* tangents) {
3084 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3085 return;
3086 }
3087 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3088 return;
3089 }
3090 SkScalar roots[2];
3091 SkScalar A = pts[2].fY;
3092 SkScalar B = pts[1].fY * w - y * w + y;
3093 SkScalar C = pts[0].fY;
3094 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3095 B -= C; // B = b*w - w * yCept + yCept - a
3096 C -= y;
3097 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3098 for (int index = 0; index < n; ++index) {
3099 SkScalar t = roots[index];
3100 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3101 if (!SkScalarNearlyEqual(x, xt)) {
3102 continue;
3103 }
3104 SkConic conic(pts, w);
3105 tangents->push(conic.evalTangentAt(t));
3106 }
3107}
3108
3109static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3110 SkTDArray<SkVector>* tangents) {
3111 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3112 return;
3113 }
3114 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3115 return;
3116 }
3117 SkScalar roots[2];
3118 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3119 2 * (pts[1].fY - pts[0].fY),
3120 pts[0].fY - y,
3121 roots);
3122 for (int index = 0; index < n; ++index) {
3123 SkScalar t = roots[index];
3124 SkScalar C = pts[0].fX;
3125 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3126 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003127 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003128 if (!SkScalarNearlyEqual(x, xt)) {
3129 continue;
3130 }
3131 tangents->push(SkEvalQuadTangentAt(pts, t));
3132 }
3133}
3134
3135static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3136 SkTDArray<SkVector>* tangents) {
3137 SkScalar y0 = pts[0].fY;
3138 SkScalar y1 = pts[1].fY;
3139 if (!between(y0, y, y1)) {
3140 return;
3141 }
3142 SkScalar x0 = pts[0].fX;
3143 SkScalar x1 = pts[1].fX;
3144 if (!between(x0, x, x1)) {
3145 return;
3146 }
3147 SkScalar dx = x1 - x0;
3148 SkScalar dy = y1 - y0;
3149 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3150 return;
3151 }
3152 SkVector v;
3153 v.set(dx, dy);
3154 tangents->push(v);
3155}
3156
reed@google.com4db592c2013-10-30 17:39:43 +00003157static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3158 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3159}
3160
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003161bool SkPath::contains(SkScalar x, SkScalar y) const {
3162 bool isInverse = this->isInverseFillType();
3163 if (this->isEmpty()) {
3164 return isInverse;
3165 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003166
reed@google.com4db592c2013-10-30 17:39:43 +00003167 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003168 return isInverse;
3169 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003170
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 SkPath::Iter iter(*this, true);
3172 bool done = false;
3173 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003174 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003175 do {
3176 SkPoint pts[4];
3177 switch (iter.next(pts, false)) {
3178 case SkPath::kMove_Verb:
3179 case SkPath::kClose_Verb:
3180 break;
3181 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003182 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003183 break;
3184 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003185 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003186 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003187 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003188 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003189 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003190 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003191 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003192 break;
3193 case SkPath::kDone_Verb:
3194 done = true;
3195 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003196 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003197 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003198 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3199 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3200 if (evenOddFill) {
3201 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003202 }
caryclark9aacd902015-12-14 08:38:09 -08003203 if (w) {
3204 return !isInverse;
3205 }
3206 if (onCurveCount <= 1) {
3207 return SkToBool(onCurveCount) ^ isInverse;
3208 }
3209 if ((onCurveCount & 1) || evenOddFill) {
3210 return SkToBool(onCurveCount & 1) ^ isInverse;
3211 }
halcanary9d524f22016-03-29 09:03:52 -07003212 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003213 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3214 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003215 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003216 SkTDArray<SkVector> tangents;
3217 do {
3218 SkPoint pts[4];
3219 int oldCount = tangents.count();
3220 switch (iter.next(pts, false)) {
3221 case SkPath::kMove_Verb:
3222 case SkPath::kClose_Verb:
3223 break;
3224 case SkPath::kLine_Verb:
3225 tangent_line(pts, x, y, &tangents);
3226 break;
3227 case SkPath::kQuad_Verb:
3228 tangent_quad(pts, x, y, &tangents);
3229 break;
3230 case SkPath::kConic_Verb:
3231 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3232 break;
3233 case SkPath::kCubic_Verb:
3234 tangent_cubic(pts, x, y, &tangents);
3235 break;
3236 case SkPath::kDone_Verb:
3237 done = true;
3238 break;
3239 }
3240 if (tangents.count() > oldCount) {
3241 int last = tangents.count() - 1;
3242 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003243 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003244 tangents.remove(last);
3245 } else {
3246 for (int index = 0; index < last; ++index) {
3247 const SkVector& test = tangents[index];
3248 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003249 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3250 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003251 tangents.remove(last);
3252 tangents.removeShuffle(index);
3253 break;
3254 }
3255 }
3256 }
3257 }
3258 } while (!done);
3259 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003260}
fmalitaaa0df4e2015-12-01 09:13:23 -08003261
3262int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3263 SkScalar w, SkPoint pts[], int pow2) {
3264 const SkConic conic(p0, p1, p2, w);
3265 return conic.chopIntoQuadsPOW2(pts, pow2);
3266}
bsalomonedc743a2016-06-01 09:42:31 -07003267
3268bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3269 unsigned* start) {
3270 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3271 return false;
3272 }
3273 SkPath::RawIter iter(path);
3274 SkPoint verbPts[4];
3275 SkPath::Verb v;
3276 SkPoint rectPts[5];
3277 int rectPtCnt = 0;
3278 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3279 switch (v) {
3280 case SkPath::kMove_Verb:
3281 if (0 != rectPtCnt) {
3282 return false;
3283 }
3284 rectPts[0] = verbPts[0];
3285 ++rectPtCnt;
3286 break;
3287 case SkPath::kLine_Verb:
3288 if (5 == rectPtCnt) {
3289 return false;
3290 }
3291 rectPts[rectPtCnt] = verbPts[1];
3292 ++rectPtCnt;
3293 break;
3294 case SkPath::kClose_Verb:
3295 if (4 == rectPtCnt) {
3296 rectPts[4] = rectPts[0];
3297 rectPtCnt = 5;
3298 }
3299 break;
3300 default:
3301 return false;
3302 }
3303 }
3304 if (rectPtCnt < 5) {
3305 return false;
3306 }
3307 if (rectPts[0] != rectPts[4]) {
3308 return false;
3309 }
bsalomon057ae8a2016-07-24 05:37:26 -07003310 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3311 // and pts 1 and 2 the opposite vertical or horizontal edge).
3312 bool vec03IsVertical;
3313 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3314 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3315 // Make sure it has non-zero width and height
3316 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003317 return false;
3318 }
bsalomon057ae8a2016-07-24 05:37:26 -07003319 vec03IsVertical = true;
3320 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3321 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3322 // Make sure it has non-zero width and height
3323 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3324 return false;
3325 }
3326 vec03IsVertical = false;
3327 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003328 return false;
3329 }
bsalomon057ae8a2016-07-24 05:37:26 -07003330 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3331 // set if it is on the bottom edge.
3332 unsigned sortFlags =
3333 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3334 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3335 switch (sortFlags) {
3336 case 0b00:
3337 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3338 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3339 *start = 0;
3340 break;
3341 case 0b01:
3342 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3343 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3344 *start = 1;
3345 break;
3346 case 0b10:
3347 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3348 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3349 *start = 3;
3350 break;
3351 case 0b11:
3352 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3353 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3354 *start = 2;
3355 break;
bsalomonedc743a2016-06-01 09:42:31 -07003356 }
bsalomonedc743a2016-06-01 09:42:31 -07003357 return true;
3358}
bsalomon21af9ca2016-08-25 12:29:23 -07003359
Brian Salomone4949402018-04-26 15:22:04 -04003360bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3361 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3362 // This gets converted to an oval.
3363 return true;
3364 }
3365 if (useCenter) {
3366 // This is a pie wedge. It's convex if the angle is <= 180.
3367 return SkScalarAbs(sweepAngle) <= 180.f;
3368 }
3369 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3370 // to a secant, i.e. convex.
3371 return SkScalarAbs(sweepAngle) <= 360.f;
3372}
3373
bsalomon21af9ca2016-08-25 12:29:23 -07003374void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3375 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3376 SkASSERT(!oval.isEmpty());
3377 SkASSERT(sweepAngle);
3378
3379 path->reset();
3380 path->setIsVolatile(true);
3381 path->setFillType(SkPath::kWinding_FillType);
3382 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3383 path->addOval(oval);
Brian Salomone4949402018-04-26 15:22:04 -04003384 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003385 return;
3386 }
3387 if (useCenter) {
3388 path->moveTo(oval.centerX(), oval.centerY());
3389 }
Brian Salomone4949402018-04-26 15:22:04 -04003390 auto firstDir =
3391 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3392 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003393 // Arc to mods at 360 and drawArc is not supposed to.
3394 bool forceMoveTo = !useCenter;
3395 while (sweepAngle <= -360.f) {
3396 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3397 startAngle -= 180.f;
3398 path->arcTo(oval, startAngle, -180.f, false);
3399 startAngle -= 180.f;
3400 forceMoveTo = false;
3401 sweepAngle += 360.f;
3402 }
3403 while (sweepAngle >= 360.f) {
3404 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3405 startAngle += 180.f;
3406 path->arcTo(oval, startAngle, 180.f, false);
3407 startAngle += 180.f;
3408 forceMoveTo = false;
3409 sweepAngle -= 360.f;
3410 }
3411 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3412 if (useCenter) {
3413 path->close();
3414 }
Brian Salomone4949402018-04-26 15:22:04 -04003415 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3416 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003417}
Mike Reed0d7dac82017-02-02 17:45:56 -08003418
3419///////////////////////////////////////////////////////////////////////////////////////////////////
3420#include "SkNx.h"
3421
3422static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3423 SkScalar ts[2];
3424 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3425 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3426 SkASSERT(n >= 0 && n <= 2);
3427 for (int i = 0; i < n; ++i) {
3428 extremas[i] = SkEvalQuadAt(src, ts[i]);
3429 }
3430 extremas[n] = src[2];
3431 return n + 1;
3432}
3433
3434static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3435 SkConic conic(src[0], src[1], src[2], w);
3436 SkScalar ts[2];
3437 int n = conic.findXExtrema(ts);
3438 n += conic.findYExtrema(&ts[n]);
3439 SkASSERT(n >= 0 && n <= 2);
3440 for (int i = 0; i < n; ++i) {
3441 extremas[i] = conic.evalAt(ts[i]);
3442 }
3443 extremas[n] = src[2];
3444 return n + 1;
3445}
3446
3447static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3448 SkScalar ts[4];
3449 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3450 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3451 SkASSERT(n >= 0 && n <= 4);
3452 for (int i = 0; i < n; ++i) {
3453 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3454 }
3455 extremas[n] = src[3];
3456 return n + 1;
3457}
3458
Mike Reed8d3196b2017-02-03 11:34:13 -05003459SkRect SkPath::computeTightBounds() const {
3460 if (0 == this->countVerbs()) {
3461 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003462 }
3463
Mike Reed8d3196b2017-02-03 11:34:13 -05003464 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3465 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003466 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003467
Mike Reed0d7dac82017-02-02 17:45:56 -08003468 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3469 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003470 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003471
3472 // initial with the first MoveTo, so we don't have to check inside the switch
3473 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003474 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003475 for (;;) {
3476 int count = 0;
3477 switch (iter.next(pts)) {
3478 case SkPath::kMove_Verb:
3479 extremas[0] = pts[0];
3480 count = 1;
3481 break;
3482 case SkPath::kLine_Verb:
3483 extremas[0] = pts[1];
3484 count = 1;
3485 break;
3486 case SkPath::kQuad_Verb:
3487 count = compute_quad_extremas(pts, extremas);
3488 break;
3489 case SkPath::kConic_Verb:
3490 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3491 break;
3492 case SkPath::kCubic_Verb:
3493 count = compute_cubic_extremas(pts, extremas);
3494 break;
3495 case SkPath::kClose_Verb:
3496 break;
3497 case SkPath::kDone_Verb:
3498 goto DONE;
3499 }
3500 for (int i = 0; i < count; ++i) {
3501 Sk2s tmp = from_point(extremas[i]);
3502 min = Sk2s::Min(min, tmp);
3503 max = Sk2s::Max(max, tmp);
3504 }
3505 }
3506DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003507 SkRect bounds;
3508 min.store((SkPoint*)&bounds.fLeft);
3509 max.store((SkPoint*)&bounds.fRight);
3510 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003511}
Cary Clarkdf429f32017-11-08 11:44:31 -05003512
3513bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3514 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3515}
3516
3517bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3518 const SkPoint& p3, bool exact) {
3519 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3520 SkPointPriv::EqualsWithinTolerance(p2, p3);
3521}
3522
3523bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3524 const SkPoint& p3, const SkPoint& p4, bool exact) {
3525 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3526 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3527 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3528 SkPointPriv::EqualsWithinTolerance(p3, p4);
3529}