blob: 497037a09d79190ca8af9f4d9e7b1cee07db2945 [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);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 SkAutoPathBoundsUpdate apbu(this, oval);
1256
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 Salomon8a98bc92018-04-25 11:21:39 -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;
1312#ifdef SK_DISABLE_ARC_TO_LINE_TO_CHECK
1313 static constexpr bool kSkipLineToCheck = true;
1314#else
1315 static constexpr bool kSkipLineToCheck = false;
1316#endif
1317 if (forceMoveTo) {
1318 this->moveTo(pt);
1319 } else if (kSkipLineToCheck || !this->getLastPt(&lastPt) ||
1320 !SkScalarNearlyEqual(lastPt.fX, pt.fX) ||
1321 !SkScalarNearlyEqual(lastPt.fY, pt.fY)) {
1322 this->lineTo(pt);
1323 }
1324 };
1325
xidachen6069dda2016-10-06 05:42:23 -07001326 // At this point, we know that the arc is not a lone point, but startV == stopV
1327 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1328 // cannot handle it.
1329 if (startV == stopV) {
1330 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1331 SkScalar radiusX = oval.width() / 2;
1332 SkScalar radiusY = oval.height() / 2;
1333 // We cannot use SkScalarSinCos function in the next line because
1334 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1335 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001336 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001337 // make sin(endAngle) to be 0 which will then draw a dot.
1338 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1339 oval.centerY() + radiusY * sk_float_sin(endAngle));
Brian Salomon8a98bc92018-04-25 11:21:39 -04001340 addPt(singlePt);
xidachen6069dda2016-10-06 05:42:23 -07001341 return;
1342 }
1343
reedd5d27d92015-02-09 13:54:43 -08001344 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001345 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001346 if (count) {
1347 this->incReserve(count * 2 + 1);
1348 const SkPoint& pt = conics[0].fPts[0];
Brian Salomon8a98bc92018-04-25 11:21:39 -04001349 addPt(pt);
reedd5d27d92015-02-09 13:54:43 -08001350 for (int i = 0; i < count; ++i) {
1351 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1352 }
reed9e779d42015-02-17 11:43:14 -08001353 } else {
Brian Salomon8a98bc92018-04-25 11:21:39 -04001354 addPt(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001355 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001356}
1357
caryclark55d49052016-01-23 05:07:04 -08001358// This converts the SVG arc to conics.
1359// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1360// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1361// See also SVG implementation notes:
1362// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1363// Note that arcSweep bool value is flipped from the original implementation.
1364void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1365 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001366 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001367 SkPoint srcPts[2];
1368 this->getLastPt(&srcPts[0]);
1369 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1370 // joining the endpoints.
1371 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1372 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001373 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001374 return;
1375 }
1376 // If the current point and target point for the arc are identical, it should be treated as a
1377 // zero length path. This ensures continuity in animations.
1378 srcPts[1].set(x, y);
1379 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001380 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001381 return;
1382 }
1383 rx = SkScalarAbs(rx);
1384 ry = SkScalarAbs(ry);
1385 SkVector midPointDistance = srcPts[0] - srcPts[1];
1386 midPointDistance *= 0.5f;
1387
1388 SkMatrix pointTransform;
1389 pointTransform.setRotate(-angle);
1390
1391 SkPoint transformedMidPoint;
1392 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1393 SkScalar squareRx = rx * rx;
1394 SkScalar squareRy = ry * ry;
1395 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1396 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1397
1398 // Check if the radii are big enough to draw the arc, scale radii if not.
1399 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1400 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1401 if (radiiScale > 1) {
1402 radiiScale = SkScalarSqrt(radiiScale);
1403 rx *= radiiScale;
1404 ry *= radiiScale;
1405 }
1406
1407 pointTransform.setScale(1 / rx, 1 / ry);
1408 pointTransform.preRotate(-angle);
1409
1410 SkPoint unitPts[2];
1411 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1412 SkVector delta = unitPts[1] - unitPts[0];
1413
1414 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1415 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1416
1417 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1418 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1419 scaleFactor = -scaleFactor;
1420 }
1421 delta.scale(scaleFactor);
1422 SkPoint centerPoint = unitPts[0] + unitPts[1];
1423 centerPoint *= 0.5f;
1424 centerPoint.offset(-delta.fY, delta.fX);
1425 unitPts[0] -= centerPoint;
1426 unitPts[1] -= centerPoint;
1427 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1428 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1429 SkScalar thetaArc = theta2 - theta1;
1430 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1431 thetaArc += SK_ScalarPI * 2;
1432 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1433 thetaArc -= SK_ScalarPI * 2;
1434 }
1435 pointTransform.setRotate(angle);
1436 pointTransform.preScale(rx, ry);
1437
Cary Clark1acd3c72017-12-08 11:37:01 -05001438#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001439 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001440#else
1441 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1442 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1443#endif
caryclark55d49052016-01-23 05:07:04 -08001444 SkScalar thetaWidth = thetaArc / segments;
1445 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1446 if (!SkScalarIsFinite(t)) {
1447 return;
1448 }
1449 SkScalar startTheta = theta1;
1450 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001451#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1452 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1453 return scalar == SkScalarFloorToScalar(scalar);
1454 };
1455 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1456 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1457 scalar_is_integer(x) && scalar_is_integer(y);
1458#endif
caryclark55d49052016-01-23 05:07:04 -08001459 for (int i = 0; i < segments; ++i) {
1460 SkScalar endTheta = startTheta + thetaWidth;
1461 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1462
1463 unitPts[1].set(cosEndTheta, sinEndTheta);
1464 unitPts[1] += centerPoint;
1465 unitPts[0] = unitPts[1];
1466 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1467 SkPoint mapped[2];
1468 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001469 /*
1470 Computing the arc width introduces rounding errors that cause arcs to start
1471 outside their marks. A round rect may lose convexity as a result. If the input
1472 values are on integers, place the conic on integers as well.
1473 */
1474#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1475 if (expectIntegers) {
1476 SkScalar* mappedScalars = &mapped[0].fX;
1477 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1478 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1479 }
1480 }
1481#endif
caryclark55d49052016-01-23 05:07:04 -08001482 this->conicTo(mapped[0], mapped[1], w);
1483 startTheta = endTheta;
1484 }
1485}
1486
1487void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1488 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1489 SkPoint currentPoint;
1490 this->getLastPt(&currentPoint);
1491 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1492}
1493
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001494void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495 if (oval.isEmpty() || 0 == sweepAngle) {
1496 return;
1497 }
1498
1499 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1500
1501 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001502 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1503 // See SkPath::addOval() docs.
1504 SkScalar startOver90 = startAngle / 90.f;
1505 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1506 SkScalar error = startOver90 - startOver90I;
1507 if (SkScalarNearlyEqual(error, 0)) {
1508 // Index 1 is at startAngle == 0.
1509 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1510 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1511 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1512 (unsigned) startIndex);
1513 return;
1514 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 }
bsalomon1978ce22016-05-31 10:42:16 -07001516 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517}
1518
1519/*
1520 Need to handle the case when the angle is sharp, and our computed end-points
1521 for the arc go behind pt1 and/or p2...
1522*/
reedc7789042015-01-29 12:59:11 -08001523void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001524 if (radius == 0) {
1525 this->lineTo(x1, y1);
1526 return;
1527 }
1528
1529 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001530
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 // need to know our prev pt so we can construct tangent vectors
1532 {
1533 SkPoint start;
1534 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001535 // Handle degenerate cases by adding a line to the first point and
1536 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 before.setNormalize(x1 - start.fX, y1 - start.fY);
1538 after.setNormalize(x2 - x1, y2 - y1);
1539 }
reed@google.comabf15c12011-01-18 20:35:51 +00001540
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 SkScalar cosh = SkPoint::DotProduct(before, after);
1542 SkScalar sinh = SkPoint::CrossProduct(before, after);
1543
1544 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001545 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 return;
1547 }
reed@google.comabf15c12011-01-18 20:35:51 +00001548
Mike Reeda99b6ce2017-02-04 11:04:26 -05001549 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550
Mike Reeda99b6ce2017-02-04 11:04:26 -05001551 SkScalar xx = x1 - dist * before.fX;
1552 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001553 after.setLength(dist);
1554 this->lineTo(xx, yy);
1555 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1556 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557}
1558
1559///////////////////////////////////////////////////////////////////////////////
1560
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001561void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 SkMatrix matrix;
1563
1564 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001565 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566}
1567
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001568void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001569 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001570
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001571 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 SkPoint pts[4];
1573 Verb verb;
1574
Cary Clark9480d822017-10-19 18:01:13 -04001575 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001576 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001577 while ((verb = iter.next(pts)) != kDone_Verb) {
1578 switch (verb) {
1579 case kMove_Verb:
1580 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001581 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1582 injectMoveToIfNeeded(); // In case last contour is closed
1583 this->lineTo(pts[0]);
1584 } else {
1585 this->moveTo(pts[0]);
1586 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 break;
1588 case kLine_Verb:
1589 proc(matrix, &pts[1], &pts[1], 1);
1590 this->lineTo(pts[1]);
1591 break;
1592 case kQuad_Verb:
1593 proc(matrix, &pts[1], &pts[1], 2);
1594 this->quadTo(pts[1], pts[2]);
1595 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001596 case kConic_Verb:
1597 proc(matrix, &pts[1], &pts[1], 2);
1598 this->conicTo(pts[1], pts[2], iter.conicWeight());
1599 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001600 case kCubic_Verb:
1601 proc(matrix, &pts[1], &pts[1], 3);
1602 this->cubicTo(pts[1], pts[2], pts[3]);
1603 break;
1604 case kClose_Verb:
1605 this->close();
1606 break;
1607 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001608 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001610 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 }
1612}
1613
1614///////////////////////////////////////////////////////////////////////////////
1615
reed@google.com277c3f82013-05-31 15:17:50 +00001616static int pts_in_verb(unsigned verb) {
1617 static const uint8_t gPtsInVerb[] = {
1618 1, // kMove
1619 1, // kLine
1620 2, // kQuad
1621 2, // kConic
1622 3, // kCubic
1623 0, // kClose
1624 0 // kDone
1625 };
1626
1627 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1628 return gPtsInVerb[verb];
1629}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631// ignore the last point of the 1st contour
1632void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001633 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1634 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 return;
1636 }
caryclark51c56782016-11-07 05:09:28 -08001637 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1638 SkASSERT(verbsEnd[0] == kMove_Verb);
1639 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1640 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641
caryclark51c56782016-11-07 05:09:28 -08001642 while (verbs < verbsEnd) {
1643 uint8_t v = *verbs++;
1644 pts -= pts_in_verb(v);
1645 switch (v) {
1646 case kMove_Verb:
1647 // if the path has multiple contours, stop after reversing the last
1648 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001650 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 break;
1652 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001653 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001655 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001656 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001657 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001659 this->cubicTo(pts[2], pts[1], pts[0]);
1660 break;
1661 case kClose_Verb:
1662 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 break;
1664 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001665 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 break;
1667 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 }
1669}
1670
reed@google.com63d73742012-01-10 15:33:12 +00001671void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001674 const SkPoint* pts = src.fPathRef->pointsEnd();
1675 // we will iterator through src's verbs backwards
1676 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1677 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001678 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001679
1680 bool needMove = true;
1681 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001682 while (verbs < verbsEnd) {
1683 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001684 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001685
1686 if (needMove) {
1687 --pts;
1688 this->moveTo(pts->fX, pts->fY);
1689 needMove = false;
1690 }
1691 pts -= n;
1692 switch (v) {
1693 case kMove_Verb:
1694 if (needClose) {
1695 this->close();
1696 needClose = false;
1697 }
1698 needMove = true;
1699 pts += 1; // so we see the point in "if (needMove)" above
1700 break;
1701 case kLine_Verb:
1702 this->lineTo(pts[0]);
1703 break;
1704 case kQuad_Verb:
1705 this->quadTo(pts[1], pts[0]);
1706 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001707 case kConic_Verb:
1708 this->conicTo(pts[1], pts[0], *--conicWeights);
1709 break;
reed@google.com63d73742012-01-10 15:33:12 +00001710 case kCubic_Verb:
1711 this->cubicTo(pts[2], pts[1], pts[0]);
1712 break;
1713 case kClose_Verb:
1714 needClose = true;
1715 break;
1716 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001717 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001718 }
1719 }
1720}
1721
reed@android.com8a1c16f2008-12-17 15:59:43 +00001722///////////////////////////////////////////////////////////////////////////////
1723
1724void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1725 SkMatrix matrix;
1726
1727 matrix.setTranslate(dx, dy);
1728 this->transform(matrix, dst);
1729}
1730
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1732 int level = 2) {
1733 if (--level >= 0) {
1734 SkPoint tmp[7];
1735
1736 SkChopCubicAtHalf(pts, tmp);
1737 subdivide_cubic_to(path, &tmp[0], level);
1738 subdivide_cubic_to(path, &tmp[3], level);
1739 } else {
1740 path->cubicTo(pts[1], pts[2], pts[3]);
1741 }
1742}
1743
1744void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1745 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001746 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 dst = (SkPath*)this;
1748 }
1749
tomhudson@google.com8d430182011-06-06 19:11:19 +00001750 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 SkPath tmp;
1752 tmp.fFillType = fFillType;
1753
1754 SkPath::Iter iter(*this, false);
1755 SkPoint pts[4];
1756 SkPath::Verb verb;
1757
reed@google.com4a3b7142012-05-16 17:16:46 +00001758 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759 switch (verb) {
1760 case kMove_Verb:
1761 tmp.moveTo(pts[0]);
1762 break;
1763 case kLine_Verb:
1764 tmp.lineTo(pts[1]);
1765 break;
1766 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001767 // promote the quad to a conic
1768 tmp.conicTo(pts[1], pts[2],
1769 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001771 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001772 tmp.conicTo(pts[1], pts[2],
1773 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001774 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 case kCubic_Verb:
1776 subdivide_cubic_to(&tmp, pts);
1777 break;
1778 case kClose_Verb:
1779 tmp.close();
1780 break;
1781 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001782 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 break;
1784 }
1785 }
1786
1787 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 SkPathRef::Editor ed(&dst->fPathRef);
1789 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001790 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001792 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1793
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001796 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001797 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001799
reed026beb52015-06-10 14:23:15 -07001800 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1801 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001802 } else {
1803 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001804 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1805 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001806 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001807 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1808 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001809 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001810 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001811 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001812 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001813 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001814 }
1815 }
1816
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 SkDEBUGCODE(dst->validate();)
1818 }
1819}
1820
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821///////////////////////////////////////////////////////////////////////////////
1822///////////////////////////////////////////////////////////////////////////////
1823
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001825 kEmptyContour_SegmentState, // The current contour is empty. We may be
1826 // starting processing or we may have just
1827 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1829 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1830 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831};
1832
1833SkPath::Iter::Iter() {
1834#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001835 fPts = nullptr;
1836 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001839 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840#endif
1841 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001842 fVerbs = nullptr;
1843 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001844 fNeedClose = false;
1845}
1846
1847SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1848 this->setPath(path, forceClose);
1849}
1850
1851void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001852 fPts = path.fPathRef->points();
1853 fVerbs = path.fPathRef->verbs();
1854 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001855 fConicWeights = path.fPathRef->conicWeights();
1856 if (fConicWeights) {
1857 fConicWeights -= 1; // begin one behind
1858 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001860 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 fForceClose = SkToU8(forceClose);
1862 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001863 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864}
1865
1866bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001867 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 return false;
1869 }
1870 if (fForceClose) {
1871 return true;
1872 }
1873
1874 const uint8_t* verbs = fVerbs;
1875 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001876
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001877 if (kMove_Verb == *(verbs - 1)) {
1878 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 }
1880
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001881 while (verbs > stop) {
1882 // verbs points one beyond the current verb, decrement first.
1883 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 if (kMove_Verb == v) {
1885 break;
1886 }
1887 if (kClose_Verb == v) {
1888 return true;
1889 }
1890 }
1891 return false;
1892}
1893
1894SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001895 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001896 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001897 // A special case: if both points are NaN, SkPoint::operation== returns
1898 // false, but the iterator expects that they are treated as the same.
1899 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001900 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1901 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001902 return kClose_Verb;
1903 }
1904
reed@google.com9e25dbf2012-05-15 17:05:38 +00001905 pts[0] = fLastPt;
1906 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 fLastPt = fMoveTo;
1908 fCloseLine = true;
1909 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001910 } else {
1911 pts[0] = fMoveTo;
1912 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914}
1915
reed@google.com9e25dbf2012-05-15 17:05:38 +00001916const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 if (fSegmentState == kAfterMove_SegmentState) {
1918 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001920 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1923 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926}
1927
caryclarke8c56662015-07-14 11:19:26 -07001928void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001929 // We need to step over anything that will not move the current draw point
1930 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001931 const uint8_t* lastMoveVerb = nullptr;
1932 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001933 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 SkPoint lastPt = fLastPt;
1935 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001936 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 switch (verb) {
1938 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 // Keep a record of this most recent move
1940 lastMoveVerb = fVerbs;
1941 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001942 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001943 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001944 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 fPts++;
1946 break;
1947
1948 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001949 // A close when we are in a segment is always valid except when it
1950 // follows a move which follows a segment.
1951 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 return;
1953 }
1954 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 break;
1957
1958 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001959 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 if (lastMoveVerb) {
1961 fVerbs = lastMoveVerb;
1962 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001963 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 return;
1965 }
1966 return;
1967 }
1968 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001969 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 fPts++;
1971 break;
1972
reed@google.com277c3f82013-05-31 15:17:50 +00001973 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001975 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 if (lastMoveVerb) {
1977 fVerbs = lastMoveVerb;
1978 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001979 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 return;
1981 }
1982 return;
1983 }
1984 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001985 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001986 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001987 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 break;
1989
1990 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001991 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 if (lastMoveVerb) {
1993 fVerbs = lastMoveVerb;
1994 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001995 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001996 return;
1997 }
1998 return;
1999 }
2000 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002001 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 fPts += 3;
2003 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002004
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002005 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002006 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002007 }
2008 }
2009}
2010
reed@google.com4a3b7142012-05-16 17:16:46 +00002011SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002012 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002013
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002015 // Close the curve if requested and if there is some curve to close
2016 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002017 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 return kLine_Verb;
2019 }
2020 fNeedClose = false;
2021 return kClose_Verb;
2022 }
2023 return kDone_Verb;
2024 }
2025
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002026 // fVerbs is one beyond the current verb, decrement first
2027 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002028 const SkPoint* SK_RESTRICT srcPts = fPts;
2029 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030
2031 switch (verb) {
2032 case kMove_Verb:
2033 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002034 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 verb = this->autoClose(pts);
2036 if (verb == kClose_Verb) {
2037 fNeedClose = false;
2038 }
2039 return (Verb)verb;
2040 }
2041 if (fVerbs == fVerbStop) { // might be a trailing moveto
2042 return kDone_Verb;
2043 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002044 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002045 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002047 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002048 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 fNeedClose = fForceClose;
2050 break;
2051 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002052 pts[0] = this->cons_moveTo();
2053 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 fLastPt = srcPts[0];
2055 fCloseLine = false;
2056 srcPts += 1;
2057 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002058 case kConic_Verb:
2059 fConicWeights += 1;
2060 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002062 pts[0] = this->cons_moveTo();
2063 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 fLastPt = srcPts[1];
2065 srcPts += 2;
2066 break;
2067 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002068 pts[0] = this->cons_moveTo();
2069 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002070 fLastPt = srcPts[2];
2071 srcPts += 3;
2072 break;
2073 case kClose_Verb:
2074 verb = this->autoClose(pts);
2075 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002076 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002077 } else {
2078 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002079 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002080 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002081 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002082 break;
2083 }
2084 fPts = srcPts;
2085 return (Verb)verb;
2086}
2087
2088///////////////////////////////////////////////////////////////////////////////
2089
Ben Wagner4d1955c2017-03-10 13:08:15 -05002090#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002091#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002092#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002093
reed@google.com51bbe752013-01-17 22:07:50 +00002094static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002095 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002096 str->append(label);
2097 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002098
reed@google.com51bbe752013-01-17 22:07:50 +00002099 const SkScalar* values = &pts[0].fX;
2100 count *= 2;
2101
2102 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002103 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002104 if (i < count - 1) {
2105 str->append(", ");
2106 }
2107 }
Mike Reed405b9d22018-01-09 15:11:08 -05002108 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002109 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002110 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002111 }
caryclark08fa28c2014-10-23 13:08:56 -07002112 str->append(");");
reede05fed02014-12-15 07:59:53 -08002113 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002114 str->append(" // ");
2115 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002116 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002117 if (i < count - 1) {
2118 str->append(", ");
2119 }
2120 }
2121 if (conicWeight >= 0) {
2122 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002123 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002124 }
2125 }
2126 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002127}
2128
caryclarke9562592014-09-15 09:26:09 -07002129void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002130 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 Iter iter(*this, forceClose);
2132 SkPoint pts[4];
2133 Verb verb;
2134
reed@google.com51bbe752013-01-17 22:07:50 +00002135 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002136 char const * const gFillTypeStrs[] = {
2137 "Winding",
2138 "EvenOdd",
2139 "InverseWinding",
2140 "InverseEvenOdd",
2141 };
2142 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2143 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002144 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 switch (verb) {
2146 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002147 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 break;
2149 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002150 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 break;
2152 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002153 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002155 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002156 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002157 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002159 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 break;
2161 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002162 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 break;
2164 default:
2165 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2166 verb = kDone_Verb; // stop the loop
2167 break;
2168 }
caryclark1049f122015-04-20 08:31:59 -07002169 if (!wStream && builder.size()) {
2170 SkDebugf("%s", builder.c_str());
2171 builder.reset();
2172 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 }
caryclark66a5d8b2014-06-24 08:30:15 -07002174 if (wStream) {
2175 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002176 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177}
2178
reed@android.come522ca52009-11-23 20:10:41 +00002179void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002180 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002181}
2182
2183void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002184 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002185}
2186
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002187
Cary Clark0461e9f2017-08-25 15:13:38 -04002188bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002189 if ((fFillType & ~3) != 0) {
2190 return false;
2191 }
reed@google.comabf15c12011-01-18 20:35:51 +00002192
djsollen@google.com077348c2012-10-22 20:23:32 +00002193#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002194 if (!fBoundsIsDirty) {
2195 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002196
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002197 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002198 if (SkToBool(fIsFinite) != isFinite) {
2199 return false;
2200 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002201
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002202 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002203 // if we're empty, fBounds may be empty but translated, so we can't
2204 // necessarily compare to bounds directly
2205 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2206 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002207 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2208 return false;
2209 }
reed@android.come522ca52009-11-23 20:10:41 +00002210 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002211 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002212 if (!fBounds.isEmpty()) {
2213 return false;
2214 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002215 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002216 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002217 if (!fBounds.contains(bounds)) {
2218 return false;
2219 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002220 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002221 }
reed@android.come522ca52009-11-23 20:10:41 +00002222 }
2223 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002224#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002225 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002226}
reed@android.come522ca52009-11-23 20:10:41 +00002227
reed@google.com04863fa2011-05-15 04:08:24 +00002228///////////////////////////////////////////////////////////////////////////////
2229
reed@google.com0b7b9822011-05-16 12:29:27 +00002230static int sign(SkScalar x) { return x < 0; }
2231#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002232
robertphillipsc506e302014-09-16 09:43:31 -07002233enum DirChange {
2234 kLeft_DirChange,
2235 kRight_DirChange,
2236 kStraight_DirChange,
2237 kBackwards_DirChange,
2238
2239 kInvalid_DirChange
2240};
2241
2242
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002243static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002244 // The error epsilon was empirically derived; worse case round rects
2245 // with a mid point outset by 2x float epsilon in tests had an error
2246 // of 12.
2247 const int epsilon = 16;
2248 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2249 return false;
2250 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002251 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002252 int aBits = SkFloatAs2sCompliment(compA);
2253 int bBits = SkFloatAs2sCompliment(compB);
2254 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002255}
2256
caryclarkb4216502015-03-02 13:02:34 -08002257static bool approximately_zero_when_compared_to(double x, double y) {
2258 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002259}
2260
caryclarkb4216502015-03-02 13:02:34 -08002261
reed@google.com04863fa2011-05-15 04:08:24 +00002262// only valid for a single contour
2263struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002264 Convexicator()
2265 : fPtCount(0)
2266 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002267 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002268 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002269 , fIsCurve(false)
2270 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002271 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002272 // warnings
djsollen2f124632016-04-29 13:53:05 -07002273 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002274 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002275 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002276 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002277 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002278
2279 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002280 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002281 }
2282
2283 SkPath::Convexity getConvexity() const { return fConvexity; }
2284
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002285 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002286 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002287
reed@google.com04863fa2011-05-15 04:08:24 +00002288 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002289 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002290 return;
2291 }
2292
2293 if (0 == fPtCount) {
2294 fCurrPt = pt;
2295 ++fPtCount;
2296 } else {
2297 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002298 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002299 if (!SkScalarIsFinite(lengthSqd)) {
2300 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002301 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002302 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002303 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002304 fCurrPt = pt;
2305 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002306 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002307 } else {
2308 SkASSERT(fPtCount > 2);
2309 this->addVec(vec);
2310 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002311
reed@google.com85b6e392011-05-15 20:25:17 +00002312 int sx = sign(vec.fX);
2313 int sy = sign(vec.fY);
2314 fDx += (sx != fSx);
2315 fDy += (sy != fSy);
2316 fSx = sx;
2317 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002318
reed@google.com85b6e392011-05-15 20:25:17 +00002319 if (fDx > 3 || fDy > 3) {
2320 fConvexity = SkPath::kConcave_Convexity;
2321 }
reed@google.com04863fa2011-05-15 04:08:24 +00002322 }
2323 }
2324 }
2325
2326 void close() {
2327 if (fPtCount > 2) {
2328 this->addVec(fFirstVec);
2329 }
2330 }
2331
caryclarkb4216502015-03-02 13:02:34 -08002332 DirChange directionChange(const SkVector& curVec) {
2333 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2334
2335 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2336 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2337 largest = SkTMax(largest, -smallest);
2338
2339 if (!almost_equal(largest, largest + cross)) {
2340 int sign = SkScalarSignAsInt(cross);
2341 if (sign) {
2342 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2343 }
2344 }
2345
2346 if (cross) {
2347 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2348 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2349 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2350 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2351 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2352 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2353 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2354 if (sign) {
2355 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2356 }
2357 }
2358 }
2359
Cary Clarkdf429f32017-11-08 11:44:31 -05002360 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2361 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2362 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2363 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002364 fLastVec.dot(curVec) < 0.0f) {
2365 return kBackwards_DirChange;
2366 }
2367
2368 return kStraight_DirChange;
2369 }
2370
Cary Clarkc8323aa2017-08-25 08:04:43 -04002371 bool hasBackwards() const {
2372 return fBackwards;
2373 }
caryclarkb4216502015-03-02 13:02:34 -08002374
caryclarkd3d1a982014-12-08 04:57:38 -08002375 bool isFinite() const {
2376 return fIsFinite;
2377 }
2378
caryclark5ccef572015-03-02 10:07:56 -08002379 void setCurve(bool isCurve) {
2380 fIsCurve = isCurve;
2381 }
2382
reed@google.com04863fa2011-05-15 04:08:24 +00002383private:
2384 void addVec(const SkVector& vec) {
2385 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002386 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002387 switch (dir) {
2388 case kLeft_DirChange: // fall through
2389 case kRight_DirChange:
2390 if (kInvalid_DirChange == fExpectedDir) {
2391 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002392 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2393 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002394 } else if (dir != fExpectedDir) {
2395 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002396 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002397 }
2398 fLastVec = vec;
2399 break;
2400 case kStraight_DirChange:
2401 break;
2402 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002403 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002404 // If any of the subsequent dir is non-backward, it'll be concave.
2405 // Otherwise, it's still convex.
2406 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002407 }
robertphillipsc506e302014-09-16 09:43:31 -07002408 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002409 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002410 break;
2411 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002412 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002413 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002414 }
2415 }
2416
caryclarkb4216502015-03-02 13:02:34 -08002417 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002418 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002419 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002420 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2421 // value with the current vec is deemed to be of a significant value.
2422 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002423 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002424 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002425 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002426 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002427 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002428 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002429 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002430 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002431};
2432
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002433SkPath::Convexity SkPath::internalGetConvexity() const {
Yuqian Li46b08122018-04-25 16:40:25 -04002434 // Sometimes we think we need to calculate convexity but another thread already did.
2435 for (auto c = (Convexity)fConvexity; c != kUnknown_Convexity; ) {
2436 return c;
2437 }
2438
reed@google.com04863fa2011-05-15 04:08:24 +00002439 SkPoint pts[4];
2440 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002441 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002442
2443 int contourCount = 0;
2444 int count;
2445 Convexicator state;
2446
caryclarkd3d1a982014-12-08 04:57:38 -08002447 if (!isFinite()) {
2448 return kUnknown_Convexity;
2449 }
Brian Osman205a1262017-09-18 13:13:48 +00002450 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002451 switch (verb) {
2452 case kMove_Verb:
2453 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002454 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002455 return kConcave_Convexity;
2456 }
2457 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002458 // fall through
2459 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002460 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002461 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002462 break;
caryclark5ccef572015-03-02 10:07:56 -08002463 case kQuad_Verb:
2464 // fall through
2465 case kConic_Verb:
2466 // fall through
2467 case kCubic_Verb:
2468 count = 2 + (kCubic_Verb == verb);
2469 // As an additional enhancement, this could set curve true only
2470 // if the curve is nonlinear
2471 state.setCurve(true);
2472 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002473 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002474 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002475 state.close();
2476 count = 0;
2477 break;
2478 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002479 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002480 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002481 return kConcave_Convexity;
2482 }
2483
2484 for (int i = 1; i <= count; i++) {
2485 state.addPt(pts[i]);
2486 }
2487 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002488 if (!state.isFinite()) {
2489 return kUnknown_Convexity;
2490 }
reed@google.com04863fa2011-05-15 04:08:24 +00002491 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002492 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002493 return kConcave_Convexity;
2494 }
2495 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002496 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002497 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002498 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2499 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2500 fConvexity = Convexity::kConcave_Convexity;
2501 } else {
2502 fFirstDirection = state.getFirstDirection();
2503 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002504 }
2505 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002506}
reed@google.com69a99432012-01-10 18:00:10 +00002507
2508///////////////////////////////////////////////////////////////////////////////
2509
2510class ContourIter {
2511public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002512 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002513
2514 bool done() const { return fDone; }
2515 // if !done() then these may be called
2516 int count() const { return fCurrPtCount; }
2517 const SkPoint* pts() const { return fCurrPt; }
2518 void next();
2519
2520private:
2521 int fCurrPtCount;
2522 const SkPoint* fCurrPt;
2523 const uint8_t* fCurrVerb;
2524 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002525 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002526 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002527 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002528};
2529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530ContourIter::ContourIter(const SkPathRef& pathRef) {
2531 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002532 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002533 fCurrPt = pathRef.points();
2534 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002535 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002536 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002537 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002538 this->next();
2539}
2540
2541void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002542 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002543 fDone = true;
2544 }
2545 if (fDone) {
2546 return;
2547 }
2548
2549 // skip pts of prev contour
2550 fCurrPt += fCurrPtCount;
2551
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002552 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002553 int ptCount = 1; // moveTo
2554 const uint8_t* verbs = fCurrVerb;
2555
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002556 for (--verbs; verbs > fStopVerbs; --verbs) {
2557 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002558 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002559 goto CONTOUR_END;
2560 case SkPath::kLine_Verb:
2561 ptCount += 1;
2562 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002563 case SkPath::kConic_Verb:
2564 fCurrConicWeight += 1;
2565 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002566 case SkPath::kQuad_Verb:
2567 ptCount += 2;
2568 break;
2569 case SkPath::kCubic_Verb:
2570 ptCount += 3;
2571 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002572 case SkPath::kClose_Verb:
2573 break;
2574 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002575 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002576 break;
2577 }
2578 }
2579CONTOUR_END:
2580 fCurrPtCount = ptCount;
2581 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002582 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002583}
2584
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002585// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002586static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002587 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2588 // We may get 0 when the above subtracts underflow. We expect this to be
2589 // very rare and lazily promote to double.
2590 if (0 == cross) {
2591 double p0x = SkScalarToDouble(p0.fX);
2592 double p0y = SkScalarToDouble(p0.fY);
2593
2594 double p1x = SkScalarToDouble(p1.fX);
2595 double p1y = SkScalarToDouble(p1.fY);
2596
2597 double p2x = SkScalarToDouble(p2.fX);
2598 double p2y = SkScalarToDouble(p2.fY);
2599
2600 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2601 (p1y - p0y) * (p2x - p0x));
2602
2603 }
2604 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002605}
2606
reed@google.comc1ea60a2012-01-31 15:15:36 +00002607// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002608static int find_max_y(const SkPoint pts[], int count) {
2609 SkASSERT(count > 0);
2610 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002611 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002612 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002613 SkScalar y = pts[i].fY;
2614 if (y > max) {
2615 max = y;
2616 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002617 }
2618 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002619 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002620}
2621
reed@google.comcabaf1d2012-01-11 21:03:05 +00002622static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2623 int i = index;
2624 for (;;) {
2625 i = (i + inc) % n;
2626 if (i == index) { // we wrapped around, so abort
2627 break;
2628 }
2629 if (pts[index] != pts[i]) { // found a different point, success!
2630 break;
2631 }
2632 }
2633 return i;
2634}
2635
reed@google.comc1ea60a2012-01-31 15:15:36 +00002636/**
2637 * Starting at index, and moving forward (incrementing), find the xmin and
2638 * xmax of the contiguous points that have the same Y.
2639 */
2640static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2641 int* maxIndexPtr) {
2642 const SkScalar y = pts[index].fY;
2643 SkScalar min = pts[index].fX;
2644 SkScalar max = min;
2645 int minIndex = index;
2646 int maxIndex = index;
2647 for (int i = index + 1; i < n; ++i) {
2648 if (pts[i].fY != y) {
2649 break;
2650 }
2651 SkScalar x = pts[i].fX;
2652 if (x < min) {
2653 min = x;
2654 minIndex = i;
2655 } else if (x > max) {
2656 max = x;
2657 maxIndex = i;
2658 }
2659 }
2660 *maxIndexPtr = maxIndex;
2661 return minIndex;
2662}
2663
reed026beb52015-06-10 14:23:15 -07002664static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2665 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002666}
2667
reed@google.comac8543f2012-01-30 20:51:25 +00002668/*
2669 * We loop through all contours, and keep the computed cross-product of the
2670 * contour that contained the global y-max. If we just look at the first
2671 * contour, we may find one that is wound the opposite way (correctly) since
2672 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2673 * that is outer most (or at least has the global y-max) before we can consider
2674 * its cross product.
2675 */
reed026beb52015-06-10 14:23:15 -07002676bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002677 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2678 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002679 return true;
2680 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002681
2682 // don't want to pay the cost for computing this if it
2683 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002684 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2685 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002686 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687 return false;
2688 }
reed@google.com69a99432012-01-10 18:00:10 +00002689
reed026beb52015-06-10 14:23:15 -07002690 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002691
reed@google.comac8543f2012-01-30 20:51:25 +00002692 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002693 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002694 SkScalar ymaxCross = 0;
2695
reed@google.com69a99432012-01-10 18:00:10 +00002696 for (; !iter.done(); iter.next()) {
2697 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002698 if (n < 3) {
2699 continue;
2700 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002701
reed@google.comcabaf1d2012-01-11 21:03:05 +00002702 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002703 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002704 int index = find_max_y(pts, n);
2705 if (pts[index].fY < ymax) {
2706 continue;
2707 }
2708
2709 // If there is more than 1 distinct point at the y-max, we take the
2710 // x-min and x-max of them and just subtract to compute the dir.
2711 if (pts[(index + 1) % n].fY == pts[index].fY) {
2712 int maxIndex;
2713 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2714 if (minIndex == maxIndex) {
2715 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002716 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002717 SkASSERT(pts[minIndex].fY == pts[index].fY);
2718 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2719 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2720 // we just subtract the indices, and let that auto-convert to
2721 // SkScalar, since we just want - or + to signal the direction.
2722 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002723 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002724 TRY_CROSSPROD:
2725 // Find a next and prev index to use for the cross-product test,
2726 // but we try to find pts that form non-zero vectors from pts[index]
2727 //
2728 // Its possible that we can't find two non-degenerate vectors, so
2729 // we have to guard our search (e.g. all the pts could be in the
2730 // same place).
2731
2732 // we pass n - 1 instead of -1 so we don't foul up % operator by
2733 // passing it a negative LH argument.
2734 int prev = find_diff_pt(pts, index, n, n - 1);
2735 if (prev == index) {
2736 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002737 continue;
2738 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002739 int next = find_diff_pt(pts, index, n, 1);
2740 SkASSERT(next != index);
2741 cross = cross_prod(pts[prev], pts[index], pts[next]);
2742 // if we get a zero and the points are horizontal, then we look at the spread in
2743 // x-direction. We really should continue to walk away from the degeneracy until
2744 // there is a divergence.
2745 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2746 // construct the subtract so we get the correct Direction below
2747 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002748 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002749 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002750
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002751 if (cross) {
2752 // record our best guess so far
2753 ymax = pts[index].fY;
2754 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002755 }
2756 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002757 if (ymaxCross) {
2758 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002759 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002760 return true;
2761 } else {
2762 return false;
2763 }
reed@google.comac8543f2012-01-30 20:51:25 +00002764}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765
2766///////////////////////////////////////////////////////////////////////////////
2767
caryclark9aacd902015-12-14 08:38:09 -08002768static bool between(SkScalar a, SkScalar b, SkScalar c) {
2769 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2770 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2771 return (a - b) * (c - b) <= 0;
2772}
2773
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002774static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2775 SkScalar t) {
2776 SkScalar A = c3 + 3*(c1 - c2) - c0;
2777 SkScalar B = 3*(c2 - c1 - c1 + c0);
2778 SkScalar C = 3*(c1 - c0);
2779 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002780 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781}
2782
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783template <size_t N> static void find_minmax(const SkPoint pts[],
2784 SkScalar* minPtr, SkScalar* maxPtr) {
2785 SkScalar min, max;
2786 min = max = pts[0].fX;
2787 for (size_t i = 1; i < N; ++i) {
2788 min = SkMinScalar(min, pts[i].fX);
2789 max = SkMaxScalar(max, pts[i].fX);
2790 }
2791 *minPtr = min;
2792 *maxPtr = max;
2793}
2794
caryclark9cb5d752015-12-18 04:35:24 -08002795static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2796 if (start.fY == end.fY) {
2797 return between(start.fX, x, end.fX) && x != end.fX;
2798 } else {
2799 return x == start.fX && y == start.fY;
2800 }
2801}
2802
caryclark9aacd902015-12-14 08:38:09 -08002803static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002804 SkScalar y0 = pts[0].fY;
2805 SkScalar y3 = pts[3].fY;
2806
2807 int dir = 1;
2808 if (y0 > y3) {
2809 SkTSwap(y0, y3);
2810 dir = -1;
2811 }
2812 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 return 0;
2814 }
caryclark9cb5d752015-12-18 04:35:24 -08002815 if (checkOnCurve(x, y, pts[0], pts[3])) {
2816 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002817 return 0;
2818 }
caryclark9cb5d752015-12-18 04:35:24 -08002819 if (y == y3) {
2820 return 0;
2821 }
2822
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002823 // quickreject or quickaccept
2824 SkScalar min, max;
2825 find_minmax<4>(pts, &min, &max);
2826 if (x < min) {
2827 return 0;
2828 }
2829 if (x > max) {
2830 return dir;
2831 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002832
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002833 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002834 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002835 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002836 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002837 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002838 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002839 if (SkScalarNearlyEqual(xt, x)) {
2840 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2841 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002842 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002843 }
2844 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002845 return xt < x ? dir : 0;
2846}
2847
caryclark9aacd902015-12-14 08:38:09 -08002848static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 SkPoint dst[10];
2850 int n = SkChopCubicAtYExtrema(pts, dst);
2851 int w = 0;
2852 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002853 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002854 }
2855 return w;
2856}
2857
caryclark9aacd902015-12-14 08:38:09 -08002858static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2859 SkASSERT(src);
2860 SkASSERT(t >= 0 && t <= 1);
2861 SkScalar src2w = src[2] * w;
2862 SkScalar C = src[0];
2863 SkScalar A = src[4] - 2 * src2w + C;
2864 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002865 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002866}
2867
2868
2869static double conic_eval_denominator(SkScalar w, SkScalar t) {
2870 SkScalar B = 2 * (w - 1);
2871 SkScalar C = 1;
2872 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002873 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002874}
2875
2876static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2877 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 SkScalar y0 = pts[0].fY;
2879 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 int dir = 1;
2882 if (y0 > y2) {
2883 SkTSwap(y0, y2);
2884 dir = -1;
2885 }
caryclark9aacd902015-12-14 08:38:09 -08002886 if (y < y0 || y > y2) {
2887 return 0;
2888 }
caryclark9cb5d752015-12-18 04:35:24 -08002889 if (checkOnCurve(x, y, pts[0], pts[2])) {
2890 *onCurveCount += 1;
2891 return 0;
2892 }
caryclark9aacd902015-12-14 08:38:09 -08002893 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002894 return 0;
2895 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002896
caryclark9aacd902015-12-14 08:38:09 -08002897 SkScalar roots[2];
2898 SkScalar A = pts[2].fY;
2899 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2900 SkScalar C = pts[0].fY;
2901 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2902 B -= C; // B = b*w - w * yCept + yCept - a
2903 C -= y;
2904 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2905 SkASSERT(n <= 1);
2906 SkScalar xt;
2907 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002908 // zero roots are returned only when y0 == y
2909 // Need [0] if dir == 1
2910 // and [2] if dir == -1
2911 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002912 } else {
2913 SkScalar t = roots[0];
2914 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2915 }
2916 if (SkScalarNearlyEqual(xt, x)) {
2917 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2918 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002919 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002920 }
2921 }
2922 return xt < x ? dir : 0;
2923}
2924
2925static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2926 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2927 if (y0 == y1) {
2928 return true;
2929 }
2930 if (y0 < y1) {
2931 return y1 <= y2;
2932 } else {
2933 return y1 >= y2;
2934 }
2935}
2936
2937static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2938 int* onCurveCount) {
2939 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002940 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002941 // If the data points are very large, the conic may not be monotonic but may also
2942 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002943 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2944 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2945 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002946 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2947 }
2948 return w;
2949}
2950
2951static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2952 SkScalar y0 = pts[0].fY;
2953 SkScalar y2 = pts[2].fY;
2954
2955 int dir = 1;
2956 if (y0 > y2) {
2957 SkTSwap(y0, y2);
2958 dir = -1;
2959 }
2960 if (y < y0 || y > y2) {
2961 return 0;
2962 }
caryclark9cb5d752015-12-18 04:35:24 -08002963 if (checkOnCurve(x, y, pts[0], pts[2])) {
2964 *onCurveCount += 1;
2965 return 0;
2966 }
caryclark9aacd902015-12-14 08:38:09 -08002967 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002968 return 0;
2969 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002970 // bounds check on X (not required. is it faster?)
2971#if 0
2972 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2973 return 0;
2974 }
2975#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002976
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 SkScalar roots[2];
2978 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2979 2 * (pts[1].fY - pts[0].fY),
2980 pts[0].fY - y,
2981 roots);
2982 SkASSERT(n <= 1);
2983 SkScalar xt;
2984 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002985 // zero roots are returned only when y0 == y
2986 // Need [0] if dir == 1
2987 // and [2] if dir == -1
2988 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 } else {
2990 SkScalar t = roots[0];
2991 SkScalar C = pts[0].fX;
2992 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2993 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002994 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 }
caryclark9aacd902015-12-14 08:38:09 -08002996 if (SkScalarNearlyEqual(xt, x)) {
2997 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2998 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002999 return 0;
caryclark9aacd902015-12-14 08:38:09 -08003000 }
3001 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 return xt < x ? dir : 0;
3003}
3004
caryclark9aacd902015-12-14 08:38:09 -08003005static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 SkPoint dst[5];
3007 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003008
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3010 n = SkChopQuadAtYExtrema(pts, dst);
3011 pts = dst;
3012 }
caryclark9aacd902015-12-14 08:38:09 -08003013 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003014 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003015 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 }
3017 return w;
3018}
3019
caryclark9aacd902015-12-14 08:38:09 -08003020static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003021 SkScalar x0 = pts[0].fX;
3022 SkScalar y0 = pts[0].fY;
3023 SkScalar x1 = pts[1].fX;
3024 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003025
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003026 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003027
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003028 int dir = 1;
3029 if (y0 > y1) {
3030 SkTSwap(y0, y1);
3031 dir = -1;
3032 }
caryclark9aacd902015-12-14 08:38:09 -08003033 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003034 return 0;
3035 }
caryclark9cb5d752015-12-18 04:35:24 -08003036 if (checkOnCurve(x, y, pts[0], pts[1])) {
3037 *onCurveCount += 1;
3038 return 0;
3039 }
3040 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003041 return 0;
3042 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003043 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003044
caryclark9aacd902015-12-14 08:38:09 -08003045 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003046 // zero cross means the point is on the line, and since the case where
3047 // y of the query point is at the end point is handled above, we can be
3048 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003049 if (x != x1 || y != pts[1].fY) {
3050 *onCurveCount += 1;
3051 }
caryclark9aacd902015-12-14 08:38:09 -08003052 dir = 0;
3053 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003054 dir = 0;
3055 }
3056 return dir;
3057}
3058
caryclark9aacd902015-12-14 08:38:09 -08003059static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3060 SkTDArray<SkVector>* tangents) {
3061 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3062 && !between(pts[2].fY, y, pts[3].fY)) {
3063 return;
3064 }
3065 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3066 && !between(pts[2].fX, x, pts[3].fX)) {
3067 return;
3068 }
3069 SkPoint dst[10];
3070 int n = SkChopCubicAtYExtrema(pts, dst);
3071 for (int i = 0; i <= n; ++i) {
3072 SkPoint* c = &dst[i * 3];
3073 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003074 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003075 continue;
mbarbella276e6332016-05-31 14:44:01 -07003076 }
caryclark9aacd902015-12-14 08:38:09 -08003077 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3078 if (!SkScalarNearlyEqual(x, xt)) {
3079 continue;
3080 }
3081 SkVector tangent;
3082 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3083 tangents->push(tangent);
3084 }
3085}
3086
3087static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3088 SkTDArray<SkVector>* tangents) {
3089 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3090 return;
3091 }
3092 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3093 return;
3094 }
3095 SkScalar roots[2];
3096 SkScalar A = pts[2].fY;
3097 SkScalar B = pts[1].fY * w - y * w + y;
3098 SkScalar C = pts[0].fY;
3099 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3100 B -= C; // B = b*w - w * yCept + yCept - a
3101 C -= y;
3102 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3103 for (int index = 0; index < n; ++index) {
3104 SkScalar t = roots[index];
3105 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3106 if (!SkScalarNearlyEqual(x, xt)) {
3107 continue;
3108 }
3109 SkConic conic(pts, w);
3110 tangents->push(conic.evalTangentAt(t));
3111 }
3112}
3113
3114static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3115 SkTDArray<SkVector>* tangents) {
3116 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3117 return;
3118 }
3119 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3120 return;
3121 }
3122 SkScalar roots[2];
3123 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3124 2 * (pts[1].fY - pts[0].fY),
3125 pts[0].fY - y,
3126 roots);
3127 for (int index = 0; index < n; ++index) {
3128 SkScalar t = roots[index];
3129 SkScalar C = pts[0].fX;
3130 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3131 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003132 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003133 if (!SkScalarNearlyEqual(x, xt)) {
3134 continue;
3135 }
3136 tangents->push(SkEvalQuadTangentAt(pts, t));
3137 }
3138}
3139
3140static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3141 SkTDArray<SkVector>* tangents) {
3142 SkScalar y0 = pts[0].fY;
3143 SkScalar y1 = pts[1].fY;
3144 if (!between(y0, y, y1)) {
3145 return;
3146 }
3147 SkScalar x0 = pts[0].fX;
3148 SkScalar x1 = pts[1].fX;
3149 if (!between(x0, x, x1)) {
3150 return;
3151 }
3152 SkScalar dx = x1 - x0;
3153 SkScalar dy = y1 - y0;
3154 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3155 return;
3156 }
3157 SkVector v;
3158 v.set(dx, dy);
3159 tangents->push(v);
3160}
3161
reed@google.com4db592c2013-10-30 17:39:43 +00003162static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3163 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3164}
3165
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003166bool SkPath::contains(SkScalar x, SkScalar y) const {
3167 bool isInverse = this->isInverseFillType();
3168 if (this->isEmpty()) {
3169 return isInverse;
3170 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003171
reed@google.com4db592c2013-10-30 17:39:43 +00003172 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003173 return isInverse;
3174 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003175
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176 SkPath::Iter iter(*this, true);
3177 bool done = false;
3178 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003179 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003180 do {
3181 SkPoint pts[4];
3182 switch (iter.next(pts, false)) {
3183 case SkPath::kMove_Verb:
3184 case SkPath::kClose_Verb:
3185 break;
3186 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003187 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 break;
3189 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003190 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003191 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003192 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003193 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003194 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003195 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003196 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003197 break;
3198 case SkPath::kDone_Verb:
3199 done = true;
3200 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003201 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003202 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003203 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3204 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3205 if (evenOddFill) {
3206 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003207 }
caryclark9aacd902015-12-14 08:38:09 -08003208 if (w) {
3209 return !isInverse;
3210 }
3211 if (onCurveCount <= 1) {
3212 return SkToBool(onCurveCount) ^ isInverse;
3213 }
3214 if ((onCurveCount & 1) || evenOddFill) {
3215 return SkToBool(onCurveCount & 1) ^ isInverse;
3216 }
halcanary9d524f22016-03-29 09:03:52 -07003217 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003218 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3219 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003220 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003221 SkTDArray<SkVector> tangents;
3222 do {
3223 SkPoint pts[4];
3224 int oldCount = tangents.count();
3225 switch (iter.next(pts, false)) {
3226 case SkPath::kMove_Verb:
3227 case SkPath::kClose_Verb:
3228 break;
3229 case SkPath::kLine_Verb:
3230 tangent_line(pts, x, y, &tangents);
3231 break;
3232 case SkPath::kQuad_Verb:
3233 tangent_quad(pts, x, y, &tangents);
3234 break;
3235 case SkPath::kConic_Verb:
3236 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3237 break;
3238 case SkPath::kCubic_Verb:
3239 tangent_cubic(pts, x, y, &tangents);
3240 break;
3241 case SkPath::kDone_Verb:
3242 done = true;
3243 break;
3244 }
3245 if (tangents.count() > oldCount) {
3246 int last = tangents.count() - 1;
3247 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003248 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003249 tangents.remove(last);
3250 } else {
3251 for (int index = 0; index < last; ++index) {
3252 const SkVector& test = tangents[index];
3253 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003254 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3255 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003256 tangents.remove(last);
3257 tangents.removeShuffle(index);
3258 break;
3259 }
3260 }
3261 }
3262 }
3263 } while (!done);
3264 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003265}
fmalitaaa0df4e2015-12-01 09:13:23 -08003266
3267int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3268 SkScalar w, SkPoint pts[], int pow2) {
3269 const SkConic conic(p0, p1, p2, w);
3270 return conic.chopIntoQuadsPOW2(pts, pow2);
3271}
bsalomonedc743a2016-06-01 09:42:31 -07003272
3273bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3274 unsigned* start) {
3275 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3276 return false;
3277 }
3278 SkPath::RawIter iter(path);
3279 SkPoint verbPts[4];
3280 SkPath::Verb v;
3281 SkPoint rectPts[5];
3282 int rectPtCnt = 0;
3283 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3284 switch (v) {
3285 case SkPath::kMove_Verb:
3286 if (0 != rectPtCnt) {
3287 return false;
3288 }
3289 rectPts[0] = verbPts[0];
3290 ++rectPtCnt;
3291 break;
3292 case SkPath::kLine_Verb:
3293 if (5 == rectPtCnt) {
3294 return false;
3295 }
3296 rectPts[rectPtCnt] = verbPts[1];
3297 ++rectPtCnt;
3298 break;
3299 case SkPath::kClose_Verb:
3300 if (4 == rectPtCnt) {
3301 rectPts[4] = rectPts[0];
3302 rectPtCnt = 5;
3303 }
3304 break;
3305 default:
3306 return false;
3307 }
3308 }
3309 if (rectPtCnt < 5) {
3310 return false;
3311 }
3312 if (rectPts[0] != rectPts[4]) {
3313 return false;
3314 }
bsalomon057ae8a2016-07-24 05:37:26 -07003315 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3316 // and pts 1 and 2 the opposite vertical or horizontal edge).
3317 bool vec03IsVertical;
3318 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3319 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3320 // Make sure it has non-zero width and height
3321 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003322 return false;
3323 }
bsalomon057ae8a2016-07-24 05:37:26 -07003324 vec03IsVertical = true;
3325 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3326 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3327 // Make sure it has non-zero width and height
3328 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3329 return false;
3330 }
3331 vec03IsVertical = false;
3332 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003333 return false;
3334 }
bsalomon057ae8a2016-07-24 05:37:26 -07003335 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3336 // set if it is on the bottom edge.
3337 unsigned sortFlags =
3338 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3339 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3340 switch (sortFlags) {
3341 case 0b00:
3342 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3343 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3344 *start = 0;
3345 break;
3346 case 0b01:
3347 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3348 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3349 *start = 1;
3350 break;
3351 case 0b10:
3352 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3353 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3354 *start = 3;
3355 break;
3356 case 0b11:
3357 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3358 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3359 *start = 2;
3360 break;
bsalomonedc743a2016-06-01 09:42:31 -07003361 }
bsalomonedc743a2016-06-01 09:42:31 -07003362 return true;
3363}
bsalomon21af9ca2016-08-25 12:29:23 -07003364
Brian Salomon8a98bc92018-04-25 11:21:39 -04003365bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3366 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3367 // This gets converted to an oval.
3368 return true;
3369 }
3370 if (useCenter) {
3371 // This is a pie wedge. It's convex if the angle is <= 180.
3372 return SkScalarAbs(sweepAngle) <= 180.f;
3373 }
3374 // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped
3375 // to a secant, i.e. convex.
3376 return SkScalarAbs(sweepAngle) <= 360.f;
3377}
3378
bsalomon21af9ca2016-08-25 12:29:23 -07003379void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3380 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3381 SkASSERT(!oval.isEmpty());
3382 SkASSERT(sweepAngle);
3383
3384 path->reset();
3385 path->setIsVolatile(true);
3386 path->setFillType(SkPath::kWinding_FillType);
3387 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3388 path->addOval(oval);
Brian Salomon8a98bc92018-04-25 11:21:39 -04003389 SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect));
bsalomon21af9ca2016-08-25 12:29:23 -07003390 return;
3391 }
3392 if (useCenter) {
3393 path->moveTo(oval.centerX(), oval.centerY());
3394 }
Brian Salomon8a98bc92018-04-25 11:21:39 -04003395 auto firstDir =
3396 sweepAngle > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
3397 bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect);
bsalomon21af9ca2016-08-25 12:29:23 -07003398 // Arc to mods at 360 and drawArc is not supposed to.
3399 bool forceMoveTo = !useCenter;
3400 while (sweepAngle <= -360.f) {
3401 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3402 startAngle -= 180.f;
3403 path->arcTo(oval, startAngle, -180.f, false);
3404 startAngle -= 180.f;
3405 forceMoveTo = false;
3406 sweepAngle += 360.f;
3407 }
3408 while (sweepAngle >= 360.f) {
3409 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3410 startAngle += 180.f;
3411 path->arcTo(oval, startAngle, 180.f, false);
3412 startAngle += 180.f;
3413 forceMoveTo = false;
3414 sweepAngle -= 360.f;
3415 }
3416 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3417 if (useCenter) {
3418 path->close();
3419 }
Brian Salomon8a98bc92018-04-25 11:21:39 -04003420 path->setConvexity(convex ? SkPath::kConvex_Convexity : SkPath::kConcave_Convexity);
3421 path->fFirstDirection.store(firstDir);
bsalomon21af9ca2016-08-25 12:29:23 -07003422}
Mike Reed0d7dac82017-02-02 17:45:56 -08003423
3424///////////////////////////////////////////////////////////////////////////////////////////////////
3425#include "SkNx.h"
3426
3427static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3428 SkScalar ts[2];
3429 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3430 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3431 SkASSERT(n >= 0 && n <= 2);
3432 for (int i = 0; i < n; ++i) {
3433 extremas[i] = SkEvalQuadAt(src, ts[i]);
3434 }
3435 extremas[n] = src[2];
3436 return n + 1;
3437}
3438
3439static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3440 SkConic conic(src[0], src[1], src[2], w);
3441 SkScalar ts[2];
3442 int n = conic.findXExtrema(ts);
3443 n += conic.findYExtrema(&ts[n]);
3444 SkASSERT(n >= 0 && n <= 2);
3445 for (int i = 0; i < n; ++i) {
3446 extremas[i] = conic.evalAt(ts[i]);
3447 }
3448 extremas[n] = src[2];
3449 return n + 1;
3450}
3451
3452static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3453 SkScalar ts[4];
3454 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3455 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3456 SkASSERT(n >= 0 && n <= 4);
3457 for (int i = 0; i < n; ++i) {
3458 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3459 }
3460 extremas[n] = src[3];
3461 return n + 1;
3462}
3463
Mike Reed8d3196b2017-02-03 11:34:13 -05003464SkRect SkPath::computeTightBounds() const {
3465 if (0 == this->countVerbs()) {
3466 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003467 }
3468
Mike Reed8d3196b2017-02-03 11:34:13 -05003469 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3470 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003471 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003472
Mike Reed0d7dac82017-02-02 17:45:56 -08003473 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3474 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003475 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003476
3477 // initial with the first MoveTo, so we don't have to check inside the switch
3478 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003479 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003480 for (;;) {
3481 int count = 0;
3482 switch (iter.next(pts)) {
3483 case SkPath::kMove_Verb:
3484 extremas[0] = pts[0];
3485 count = 1;
3486 break;
3487 case SkPath::kLine_Verb:
3488 extremas[0] = pts[1];
3489 count = 1;
3490 break;
3491 case SkPath::kQuad_Verb:
3492 count = compute_quad_extremas(pts, extremas);
3493 break;
3494 case SkPath::kConic_Verb:
3495 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3496 break;
3497 case SkPath::kCubic_Verb:
3498 count = compute_cubic_extremas(pts, extremas);
3499 break;
3500 case SkPath::kClose_Verb:
3501 break;
3502 case SkPath::kDone_Verb:
3503 goto DONE;
3504 }
3505 for (int i = 0; i < count; ++i) {
3506 Sk2s tmp = from_point(extremas[i]);
3507 min = Sk2s::Min(min, tmp);
3508 max = Sk2s::Max(max, tmp);
3509 }
3510 }
3511DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003512 SkRect bounds;
3513 min.store((SkPoint*)&bounds.fLeft);
3514 max.store((SkPoint*)&bounds.fRight);
3515 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003516}
Cary Clarkdf429f32017-11-08 11:44:31 -05003517
3518bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3519 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3520}
3521
3522bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3523 const SkPoint& p3, bool exact) {
3524 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3525 SkPointPriv::EqualsWithinTolerance(p2, p3);
3526}
3527
3528bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3529 const SkPoint& p3, const SkPoint& p4, bool exact) {
3530 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3531 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3532 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3533 SkPointPriv::EqualsWithinTolerance(p3, p4);
3534}