blob: d57e95ad20bbbe6885b3f3cb0daa59786f5247c8 [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;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000143}
144
145void SkPath::resetFields() {
146 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000147 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000148 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000149 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700150 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000151
152 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700153 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000154}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155
bungeman@google.coma5809a32013-06-21 15:13:34 +0000156SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000157 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000158 this->copyFields(that);
159 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160}
161
162SkPath::~SkPath() {
163 SkDEBUGCODE(this->validate();)
164}
165
bungeman@google.coma5809a32013-06-21 15:13:34 +0000166SkPath& SkPath::operator=(const SkPath& that) {
167 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000168
bungeman@google.coma5809a32013-06-21 15:13:34 +0000169 if (this != &that) {
170 fPathRef.reset(SkRef(that.fPathRef.get()));
171 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 }
173 SkDEBUGCODE(this->validate();)
174 return *this;
175}
176
bungeman@google.coma5809a32013-06-21 15:13:34 +0000177void SkPath::copyFields(const SkPath& that) {
178 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000179 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000180 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700181 fIsVolatile = that.fIsVolatile;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400182
183 // Non-atomic assignment of atomic values.
184 fConvexity .store(that.fConvexity .load());
185 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186}
187
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000188bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000189 // note: don't need to look at isConvex or bounds, since just comparing the
190 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000191 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000192 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000193}
194
bungeman@google.coma5809a32013-06-21 15:13:34 +0000195void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000196 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700197 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000198 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700200 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400201
202 // Non-atomic swaps of atomic values.
203 Convexity c = fConvexity.load();
204 fConvexity.store(that.fConvexity.load());
205 that.fConvexity.store(c);
206
207 uint8_t fd = fFirstDirection.load();
208 fFirstDirection.store(that.fFirstDirection.load());
209 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000210 }
211}
212
caryclark8e7b19d2016-02-18 04:11:48 -0800213bool SkPath::isInterpolatable(const SkPath& compare) const {
214 int count = fPathRef->countVerbs();
215 if (count != compare.fPathRef->countVerbs()) {
216 return false;
217 }
218 if (!count) {
219 return true;
220 }
221 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
222 count)) {
223 return false;
224 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800225 return !fPathRef->countWeights() ||
226 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800227 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
228}
229
230bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500231 int pointCount = fPathRef->countPoints();
232 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800233 return false;
234 }
Cary Clark7487ec82018-03-06 09:30:46 -0500235 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800236 return true;
237 }
caryclark8e7b19d2016-02-18 04:11:48 -0800238 out->reset();
239 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700240 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800241 return true;
242}
243
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000244static inline bool check_edge_against_rect(const SkPoint& p0,
245 const SkPoint& p1,
246 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700247 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000248 const SkPoint* edgeBegin;
249 SkVector v;
reed026beb52015-06-10 14:23:15 -0700250 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000251 v = p1 - p0;
252 edgeBegin = &p0;
253 } else {
254 v = p0 - p1;
255 edgeBegin = &p1;
256 }
257 if (v.fX || v.fY) {
258 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500259 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
260 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
261 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
262 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000263 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
264 return false;
265 }
266 }
267 return true;
268}
269
270bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
271 // This only handles non-degenerate convex paths currently.
272 if (kConvex_Convexity != this->getConvexity()) {
273 return false;
274 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000275
reed026beb52015-06-10 14:23:15 -0700276 SkPathPriv::FirstDirection direction;
277 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000278 return false;
279 }
280
281 SkPoint firstPt;
282 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500283 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000284 SkPath::Verb verb;
285 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400286 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000287 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000288 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289
Lee Salzmana19f0242017-01-12 13:06:21 -0500290 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291 int nextPt = -1;
292 switch (verb) {
293 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000294 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 SkDEBUGCODE(++moveCnt);
296 firstPt = prevPt = pts[0];
297 break;
298 case kLine_Verb:
299 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000300 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400301 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000302 break;
303 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000304 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000305 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400306 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000307 nextPt = 2;
308 break;
309 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000310 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400311 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000312 nextPt = 3;
313 break;
314 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000315 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000316 break;
317 default:
318 SkDEBUGFAIL("unknown verb");
319 }
320 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800321 if (SkPath::kConic_Verb == verb) {
322 SkConic orig;
323 orig.set(pts, iter.conicWeight());
324 SkPoint quadPts[5];
325 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800326 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800327
328 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
329 return false;
330 }
331 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
332 return false;
333 }
334 } else {
335 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
336 return false;
337 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000338 }
339 prevPt = pts[nextPt];
340 }
341 }
342
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400343 if (segmentCount) {
344 return check_edge_against_rect(prevPt, firstPt, rect, direction);
345 }
346 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000347}
348
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000349uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000350 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800351#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400352 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
353 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000354#endif
355 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000356}
djsollen@google.come63793a2012-03-21 15:39:03 +0000357
reed@android.com8a1c16f2008-12-17 15:59:43 +0000358void SkPath::reset() {
359 SkDEBUGCODE(this->validate();)
360
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000361 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000362 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000363}
364
365void SkPath::rewind() {
366 SkDEBUGCODE(this->validate();)
367
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000368 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000369 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000370}
371
fsb1475b02016-01-20 09:51:07 -0800372bool SkPath::isLastContourClosed() const {
373 int verbCount = fPathRef->countVerbs();
374 if (0 == verbCount) {
375 return false;
376 }
377 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
378}
379
reed@google.com7e6c4d12012-05-10 14:05:43 +0000380bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000381 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000382
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000383 if (2 == verbCount) {
384 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
385 if (kLine_Verb == fPathRef->atVerb(1)) {
386 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000387 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000388 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 line[0] = pts[0];
390 line[1] = pts[1];
391 }
392 return true;
393 }
394 }
395 return false;
396}
397
caryclark@google.comf1316942011-07-26 19:54:45 +0000398/*
399 Determines if path is a rect by keeping track of changes in direction
400 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000401
caryclark@google.comf1316942011-07-26 19:54:45 +0000402 The direction is computed such that:
403 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000404 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000405 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000407
caryclark@google.comf1316942011-07-26 19:54:45 +0000408A rectangle cycles up/right/down/left or up/left/down/right.
409
410The test fails if:
411 The path is closed, and followed by a line.
412 A second move creates a new endpoint.
413 A diagonal line is parsed.
414 There's more than four changes of direction.
415 There's a discontinuity on the line (e.g., a move in the middle)
416 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000417 The path contains a quadratic or cubic.
418 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000419 *The rectangle doesn't complete a cycle.
420 *The final point isn't equal to the first point.
421
422 *These last two conditions we relax if we have a 3-edge path that would
423 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000424
caryclark@google.comf1316942011-07-26 19:54:45 +0000425It's OK if the path has:
426 Several colinear line segments composing a rectangle side.
427 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000428
caryclark@google.comf1316942011-07-26 19:54:45 +0000429The direction takes advantage of the corners found since opposite sides
430must travel in opposite directions.
431
432FIXME: Allow colinear quads and cubics to be treated like lines.
433FIXME: If the API passes fill-only, return true if the filled stroke
434 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000435
436 first,last,next direction state-machine:
437 0x1 is set if the segment is horizontal
438 0x2 is set if the segment is moving to the right or down
439 thus:
440 two directions are opposites iff (dirA ^ dirB) == 0x2
441 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000442
caryclark@google.comf1316942011-07-26 19:54:45 +0000443 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000444static int rect_make_dir(SkScalar dx, SkScalar dy) {
445 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
446}
caryclark@google.comf68154a2012-11-21 15:18:06 +0000447bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
448 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000449 int corners = 0;
450 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000451 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700452 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000453 first.set(0, 0);
454 last.set(0, 0);
455 int firstDirection = 0;
456 int lastDirection = 0;
457 int nextDirection = 0;
458 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000459 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700460 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000461 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000462 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700463 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
464 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000465 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000466 savePts = pts;
467 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700469 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 case kLine_Verb: {
471 SkScalar left = last.fX;
472 SkScalar top = last.fY;
473 SkScalar right = pts->fX;
474 SkScalar bottom = pts->fY;
475 ++pts;
476 if (left != right && top != bottom) {
477 return false; // diagonal
478 }
479 if (left == right && top == bottom) {
480 break; // single point on side OK
481 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000482 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000483 if (0 == corners) {
484 firstDirection = nextDirection;
485 first = last;
486 last = pts[-1];
487 corners = 1;
488 closedOrMoved = false;
489 break;
490 }
491 if (closedOrMoved) {
492 return false; // closed followed by a line
493 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000494 if (autoClose && nextDirection == firstDirection) {
495 break; // colinear with first
496 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000497 closedOrMoved = autoClose;
498 if (lastDirection != nextDirection) {
499 if (++corners > 4) {
500 return false; // too many direction changes
501 }
502 }
503 last = pts[-1];
504 if (lastDirection == nextDirection) {
505 break; // colinear segment
506 }
507 // Possible values for corners are 2, 3, and 4.
508 // When corners == 3, nextDirection opposes firstDirection.
509 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000510 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
512 if ((directionCycle ^ turn) != nextDirection) {
513 return false; // direction didn't follow cycle
514 }
515 break;
516 }
517 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000518 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000519 case kCubic_Verb:
520 return false; // quadratic, cubic not allowed
521 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700522 if (allowPartial && !autoClose && firstDirection) {
523 insertClose = true;
524 *currVerb -= 1; // try move again afterwards
525 goto addMissingClose;
526 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000527 last = *pts++;
528 closedOrMoved = true;
529 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000530 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000531 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000532 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000533 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000534 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000535 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700536addMissingClose:
537 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000538 }
539 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000540 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000541 if (!result) {
542 // check if we are just an incomplete rectangle, in which case we can
543 // return true, but not claim to be closed.
544 // e.g.
545 // 3 sided rectangle
546 // 4 sided but the last edge is not long enough to reach the start
547 //
548 SkScalar closeX = first.x() - last.x();
549 SkScalar closeY = first.y() - last.y();
550 if (closeX && closeY) {
551 return false; // we're diagonal, abort (can we ever reach this?)
552 }
553 int closeDirection = rect_make_dir(closeX, closeY);
554 // make sure the close-segment doesn't double-back on itself
555 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
556 result = true;
557 autoClose = false; // we are not closed
558 }
559 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000560 if (savePts) {
561 *ptsPtr = savePts;
562 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000563 if (result && isClosed) {
564 *isClosed = autoClose;
565 }
566 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000567 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000568 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000569 return result;
570}
571
robertphillips4f662e62014-12-29 14:06:51 -0800572bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000573 SkDEBUGCODE(this->validate();)
574 int currVerb = 0;
575 const SkPoint* pts = fPathRef->points();
robertphillipsfe7c4272014-12-29 11:36:39 -0800576 const SkPoint* first = pts;
robertphillips4f662e62014-12-29 14:06:51 -0800577 if (!this->isRectContour(false, &currVerb, &pts, isClosed, direction)) {
robertphillipsfe7c4272014-12-29 11:36:39 -0800578 return false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000579 }
robertphillipsfe7c4272014-12-29 11:36:39 -0800580 if (rect) {
robertphillips4f662e62014-12-29 14:06:51 -0800581 int32_t num = SkToS32(pts - first);
582 if (num) {
583 rect->set(first, num);
robertphillipsfe7c4272014-12-29 11:36:39 -0800584 } else {
585 // 'pts' isn't updated for open rects
586 *rect = this->getBounds();
587 }
588 }
589 return true;
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000590}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000591
caryclark95bc5f32015-04-08 08:34:15 -0700592bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000593 SkDEBUGCODE(this->validate();)
594 int currVerb = 0;
595 const SkPoint* pts = fPathRef->points();
596 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000597 Direction testDirs[2];
halcanary96fcdcc2015-08-27 07:41:13 -0700598 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000599 return false;
600 }
601 const SkPoint* last = pts;
602 SkRect testRects[2];
caryclark95bc5f32015-04-08 08:34:15 -0700603 bool isClosed;
604 if (isRectContour(false, &currVerb, &pts, &isClosed, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000605 testRects[0].set(first, SkToS32(last - first));
caryclark95bc5f32015-04-08 08:34:15 -0700606 if (!isClosed) {
607 pts = fPathRef->points() + fPathRef->countPoints();
608 }
scroggo@google.com614f9e32013-05-09 18:05:32 +0000609 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 if (testRects[0].contains(testRects[1])) {
611 if (rects) {
612 rects[0] = testRects[0];
613 rects[1] = testRects[1];
614 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000615 if (dirs) {
616 dirs[0] = testDirs[0];
617 dirs[1] = testDirs[1];
618 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000619 return true;
620 }
621 if (testRects[1].contains(testRects[0])) {
622 if (rects) {
623 rects[0] = testRects[1];
624 rects[1] = testRects[0];
625 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000626 if (dirs) {
627 dirs[0] = testDirs[1];
628 dirs[1] = testDirs[0];
629 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000630 return true;
631 }
632 }
633 return false;
634}
635
Mike Reed0c3137c2018-02-20 13:57:05 -0500636bool SkPath::isOval(SkRect* bounds) const {
637 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
638}
639
640bool SkPath::isRRect(SkRRect* rrect) const {
641 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
642}
643
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644int SkPath::countPoints() const {
645 return fPathRef->countPoints();
646}
647
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000648int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000649 SkDEBUGCODE(this->validate();)
650
651 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000652 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800654 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656}
657
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000658SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
660 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000661 }
662 return SkPoint::Make(0, 0);
663}
664
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665int SkPath::countVerbs() const {
666 return fPathRef->countVerbs();
667}
668
669static inline void copy_verbs_reverse(uint8_t* inorderDst,
670 const uint8_t* reversedSrc,
671 int count) {
672 for (int i = 0; i < count; ++i) {
673 inorderDst[i] = reversedSrc[~i];
674 }
675}
676
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000677int SkPath::getVerbs(uint8_t dst[], int max) const {
678 SkDEBUGCODE(this->validate();)
679
680 SkASSERT(max >= 0);
681 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 int count = SkMin32(max, fPathRef->countVerbs());
683 copy_verbs_reverse(dst, fPathRef->verbs(), count);
684 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000685}
686
reed@google.com294dd7b2011-10-11 11:58:32 +0000687bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688 SkDEBUGCODE(this->validate();)
689
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000690 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000691 if (count > 0) {
692 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000693 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000694 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000695 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000697 if (lastPt) {
698 lastPt->set(0, 0);
699 }
700 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000701}
702
caryclarkaec25102015-04-29 08:28:30 -0700703void SkPath::setPt(int index, SkScalar x, SkScalar y) {
704 SkDEBUGCODE(this->validate();)
705
706 int count = fPathRef->countPoints();
707 if (count <= index) {
708 return;
709 } else {
710 SkPathRef::Editor ed(&fPathRef);
711 ed.atPoint(index)->set(x, y);
712 }
713}
714
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715void SkPath::setLastPt(SkScalar x, SkScalar y) {
716 SkDEBUGCODE(this->validate();)
717
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000718 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719 if (count == 0) {
720 this->moveTo(x, y);
721 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000722 SkPathRef::Editor ed(&fPathRef);
723 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724 }
725}
726
reed@google.com04863fa2011-05-15 04:08:24 +0000727void SkPath::setConvexity(Convexity c) {
728 if (fConvexity != c) {
729 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000730 }
731}
732
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733//////////////////////////////////////////////////////////////////////////////
734// Construction methods
735
reed026beb52015-06-10 14:23:15 -0700736#define DIRTY_AFTER_EDIT \
737 do { \
738 fConvexity = kUnknown_Convexity; \
739 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000740 } while (0)
741
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742void SkPath::incReserve(U16CPU inc) {
743 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000744 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000745 SkDEBUGCODE(this->validate();)
746}
747
748void SkPath::moveTo(SkScalar x, SkScalar y) {
749 SkDEBUGCODE(this->validate();)
750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000751 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000753 // remember our index
754 fLastMoveToIndex = fPathRef->countPoints();
755
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000756 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700757
758 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000759}
760
761void SkPath::rMoveTo(SkScalar x, SkScalar y) {
762 SkPoint pt;
763 this->getLastPt(&pt);
764 this->moveTo(pt.fX + x, pt.fY + y);
765}
766
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000767void SkPath::injectMoveToIfNeeded() {
768 if (fLastMoveToIndex < 0) {
769 SkScalar x, y;
770 if (fPathRef->countVerbs() == 0) {
771 x = y = 0;
772 } else {
773 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
774 x = pt.fX;
775 y = pt.fY;
776 }
777 this->moveTo(x, y);
778 }
779}
780
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781void SkPath::lineTo(SkScalar x, SkScalar y) {
782 SkDEBUGCODE(this->validate();)
783
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000784 this->injectMoveToIfNeeded();
785
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000786 SkPathRef::Editor ed(&fPathRef);
787 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788
reed@google.comb54455e2011-05-16 14:16:04 +0000789 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790}
791
792void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000793 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 SkPoint pt;
795 this->getLastPt(&pt);
796 this->lineTo(pt.fX + x, pt.fY + y);
797}
798
799void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
800 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000801
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000802 this->injectMoveToIfNeeded();
803
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000804 SkPathRef::Editor ed(&fPathRef);
805 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806 pts[0].set(x1, y1);
807 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000808
reed@google.comb54455e2011-05-16 14:16:04 +0000809 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810}
811
812void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000813 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814 SkPoint pt;
815 this->getLastPt(&pt);
816 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
817}
818
reed@google.com277c3f82013-05-31 15:17:50 +0000819void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
820 SkScalar w) {
821 // check for <= 0 or NaN with this test
822 if (!(w > 0)) {
823 this->lineTo(x2, y2);
824 } else if (!SkScalarIsFinite(w)) {
825 this->lineTo(x1, y1);
826 this->lineTo(x2, y2);
827 } else if (SK_Scalar1 == w) {
828 this->quadTo(x1, y1, x2, y2);
829 } else {
830 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000831
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000832 this->injectMoveToIfNeeded();
833
reed@google.com277c3f82013-05-31 15:17:50 +0000834 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000835 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000836 pts[0].set(x1, y1);
837 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000838
reed@google.com277c3f82013-05-31 15:17:50 +0000839 DIRTY_AFTER_EDIT;
840 }
841}
842
843void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
844 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000845 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000846 SkPoint pt;
847 this->getLastPt(&pt);
848 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
849}
850
reed@android.com8a1c16f2008-12-17 15:59:43 +0000851void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
852 SkScalar x3, SkScalar y3) {
853 SkDEBUGCODE(this->validate();)
854
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000855 this->injectMoveToIfNeeded();
856
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 SkPathRef::Editor ed(&fPathRef);
858 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 pts[0].set(x1, y1);
860 pts[1].set(x2, y2);
861 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862
reed@google.comb54455e2011-05-16 14:16:04 +0000863 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864}
865
866void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
867 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000868 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 SkPoint pt;
870 this->getLastPt(&pt);
871 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
872 pt.fX + x3, pt.fY + y3);
873}
874
875void SkPath::close() {
876 SkDEBUGCODE(this->validate();)
877
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000878 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000880 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 case kLine_Verb:
882 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000883 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000885 case kMove_Verb: {
886 SkPathRef::Editor ed(&fPathRef);
887 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000889 }
reed@google.com277c3f82013-05-31 15:17:50 +0000890 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000891 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000892 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000893 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000894 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000895 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000896 }
897 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000898
899 // signal that we need a moveTo to follow us (unless we're done)
900#if 0
901 if (fLastMoveToIndex >= 0) {
902 fLastMoveToIndex = ~fLastMoveToIndex;
903 }
904#else
905 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
906#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000907}
908
909///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000910
fmalitac08d53e2015-11-17 09:53:29 -0800911namespace {
912
913template <unsigned N>
914class PointIterator {
915public:
916 PointIterator(SkPath::Direction dir, unsigned startIndex)
917 : fCurrent(startIndex % N)
918 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
919
920 const SkPoint& current() const {
921 SkASSERT(fCurrent < N);
922 return fPts[fCurrent];
923 }
924
925 const SkPoint& next() {
926 fCurrent = (fCurrent + fAdvance) % N;
927 return this->current();
928 }
929
930protected:
931 SkPoint fPts[N];
932
933private:
934 unsigned fCurrent;
935 unsigned fAdvance;
936};
937
938class RectPointIterator : public PointIterator<4> {
939public:
940 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
941 : PointIterator(dir, startIndex) {
942
943 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
944 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
945 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
946 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
947 }
948};
949
950class OvalPointIterator : public PointIterator<4> {
951public:
952 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
953 : PointIterator(dir, startIndex) {
954
955 const SkScalar cx = oval.centerX();
956 const SkScalar cy = oval.centerY();
957
958 fPts[0] = SkPoint::Make(cx, oval.fTop);
959 fPts[1] = SkPoint::Make(oval.fRight, cy);
960 fPts[2] = SkPoint::Make(cx, oval.fBottom);
961 fPts[3] = SkPoint::Make(oval.fLeft, cy);
962 }
963};
964
965class RRectPointIterator : public PointIterator<8> {
966public:
967 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
968 : PointIterator(dir, startIndex) {
969
970 const SkRect& bounds = rrect.getBounds();
971 const SkScalar L = bounds.fLeft;
972 const SkScalar T = bounds.fTop;
973 const SkScalar R = bounds.fRight;
974 const SkScalar B = bounds.fBottom;
975
976 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
977 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
978 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
979 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
980 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
981 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
982 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
983 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
984 }
985};
986
987} // anonymous namespace
988
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000989static void assert_known_direction(int dir) {
990 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
991}
992
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800994 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995}
996
997void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
998 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800999 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
1000}
1001
1002void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001003 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001004 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001005 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001006 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001007 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001008
fmalitac08d53e2015-11-17 09:53:29 -08001009 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001010
fmalitac08d53e2015-11-17 09:53:29 -08001011 const int kVerbs = 5; // moveTo + 3x lineTo + close
1012 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001013
fmalitac08d53e2015-11-17 09:53:29 -08001014 RectPointIterator iter(rect, dir, startIndex);
1015
1016 this->moveTo(iter.current());
1017 this->lineTo(iter.next());
1018 this->lineTo(iter.next());
1019 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001020 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001021
1022 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001023}
1024
reed@google.com744faba2012-05-29 19:54:52 +00001025void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1026 SkDEBUGCODE(this->validate();)
1027 if (count <= 0) {
1028 return;
1029 }
1030
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001031 fLastMoveToIndex = fPathRef->countPoints();
1032
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001033 // +close makes room for the extra kClose_Verb
1034 SkPathRef::Editor ed(&fPathRef, count+close, count);
1035
1036 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001037 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001038 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1039 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001040 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001041
reed@google.com744faba2012-05-29 19:54:52 +00001042 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001043 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001044 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001045 }
1046
reed@google.com744faba2012-05-29 19:54:52 +00001047 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001048 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001049}
1050
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001051#include "SkGeometry.h"
1052
reedf90ea012015-01-29 12:03:58 -08001053static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1054 SkPoint* pt) {
1055 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001056 // Chrome uses this path to move into and out of ovals. If not
1057 // treated as a special case the moves can distort the oval's
1058 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001059 pt->set(oval.fRight, oval.centerY());
1060 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061 } else if (0 == oval.width() && 0 == oval.height()) {
1062 // Chrome will sometimes create 0 radius round rects. Having degenerate
1063 // quad segments in the path prevents the path from being recognized as
1064 // a rect.
1065 // TODO: optimizing the case where only one of width or height is zero
1066 // should also be considered. This case, however, doesn't seem to be
1067 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001068 pt->set(oval.fRight, oval.fTop);
1069 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001070 }
reedf90ea012015-01-29 12:03:58 -08001071 return false;
1072}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001073
reedd5d27d92015-02-09 13:54:43 -08001074// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1075//
1076static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1077 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1078 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1079 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001080
1081 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001082 loss in radians-conversion and/or sin/cos, we may end up with coincident
1083 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1084 of drawing a nearly complete circle (good).
1085 e.g. canvas.drawArc(0, 359.99, ...)
1086 -vs- canvas.drawArc(0, 359.9, ...)
1087 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001088 */
reedd5d27d92015-02-09 13:54:43 -08001089 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001090 SkScalar sw = SkScalarAbs(sweepAngle);
1091 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1092 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1093 // make a guess at a tiny angle (in radians) to tweak by
1094 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1095 // not sure how much will be enough, so we use a loop
1096 do {
1097 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001098 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1099 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001100 }
1101 }
reedd5d27d92015-02-09 13:54:43 -08001102 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1103}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001104
reed9e779d42015-02-17 11:43:14 -08001105/**
1106 * If this returns 0, then the caller should just line-to the singlePt, else it should
1107 * ignore singlePt and append the specified number of conics.
1108 */
reedd5d27d92015-02-09 13:54:43 -08001109static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001110 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1111 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001112 SkMatrix matrix;
1113
1114 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1115 matrix.postTranslate(oval.centerX(), oval.centerY());
1116
reed9e779d42015-02-17 11:43:14 -08001117 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1118 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001119 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001120 }
1121 return count;
reedd5d27d92015-02-09 13:54:43 -08001122}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001123
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001124void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001126 SkRRect rrect;
1127 rrect.setRectRadii(rect, (const SkVector*) radii);
1128 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001129}
1130
reed@google.com4ed0fb72012-12-12 20:48:18 +00001131void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001132 // legacy start indices: 6 (CW) and 7(CCW)
1133 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1134}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001135
fmalitac08d53e2015-11-17 09:53:29 -08001136void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1137 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001138
caryclarkda707bf2015-11-19 14:47:43 -08001139 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001140 const SkRect& bounds = rrect.getBounds();
1141
Brian Salomon0a241ce2017-12-15 11:31:05 -05001142 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001143 // degenerate(rect) => radii points are collapsing
1144 this->addRect(bounds, dir, (startIndex + 1) / 2);
1145 } else if (rrect.isOval()) {
1146 // degenerate(oval) => line points are collapsing
1147 this->addOval(bounds, dir, startIndex / 2);
1148 } else {
1149 fFirstDirection = this->hasOnlyMoveTos() ?
1150 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1151
1152 SkAutoPathBoundsUpdate apbu(this, bounds);
1153 SkAutoDisableDirectionCheck addc(this);
1154
1155 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1156 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1157 const SkScalar weight = SK_ScalarRoot2Over2;
1158
1159 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1160 const int kVerbs = startsWithConic
1161 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1162 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1163 this->incReserve(kVerbs);
1164
1165 RRectPointIterator rrectIter(rrect, dir, startIndex);
1166 // Corner iterator indices follow the collapsed radii model,
1167 // adjusted such that the start pt is "behind" the radii start pt.
1168 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1169 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1170
1171 this->moveTo(rrectIter.current());
1172 if (startsWithConic) {
1173 for (unsigned i = 0; i < 3; ++i) {
1174 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1175 this->lineTo(rrectIter.next());
1176 }
1177 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1178 // final lineTo handled by close().
1179 } else {
1180 for (unsigned i = 0; i < 4; ++i) {
1181 this->lineTo(rrectIter.next());
1182 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1183 }
1184 }
1185 this->close();
1186
caryclarkda707bf2015-11-19 14:47:43 -08001187 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001188 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001189
fmalitac08d53e2015-11-17 09:53:29 -08001190 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1191 }
1192
1193 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001194}
1195
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001196bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001197 int count = fPathRef->countVerbs();
1198 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1199 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001200 if (*verbs == kLine_Verb ||
1201 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001202 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001203 *verbs == kCubic_Verb) {
1204 return false;
1205 }
1206 ++verbs;
1207 }
1208 return true;
1209}
1210
Brian Osmana2318572017-07-10 15:09:26 -04001211bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1212 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001213 if (count < 2) {
1214 return true;
1215 }
Brian Osmana2318572017-07-10 15:09:26 -04001216 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001217 const SkPoint& first = *pts;
1218 for (int index = 1; index < count; ++index) {
1219 if (first != pts[index]) {
1220 return false;
1221 }
1222 }
1223 return true;
1224}
1225
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001226void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1227 Direction dir) {
1228 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001229
humper@google.com75e3ca12013-04-08 21:44:11 +00001230 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001231 return;
1232 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001233
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001234 SkRRect rrect;
1235 rrect.setRectXY(rect, rx, ry);
1236 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001237}
1238
reed@android.com8a1c16f2008-12-17 15:59:43 +00001239void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001240 // legacy start index: 1
1241 this->addOval(oval, dir, 1);
1242}
1243
1244void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001245 assert_known_direction(dir);
1246
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001247 /* If addOval() is called after previous moveTo(),
1248 this path is still marked as an oval. This is used to
1249 fit into WebKit's calling sequences.
1250 We can't simply check isEmpty() in this case, as additional
1251 moveTo() would mark the path non empty.
1252 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001253 bool isOval = hasOnlyMoveTos();
1254 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001255 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001256 } else {
reed026beb52015-06-10 14:23:15 -07001257 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001258 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001259
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001260 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 SkAutoPathBoundsUpdate apbu(this, oval);
1262
fmalitac08d53e2015-11-17 09:53:29 -08001263 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1264 const int kVerbs = 6; // moveTo + 4x conicTo + close
1265 this->incReserve(kVerbs);
1266
1267 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1268 // The corner iterator pts are tracking "behind" the oval/radii pts.
1269 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001270 const SkScalar weight = SK_ScalarRoot2Over2;
1271
fmalitac08d53e2015-11-17 09:53:29 -08001272 this->moveTo(ovalIter.current());
1273 for (unsigned i = 0; i < 4; ++i) {
1274 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001275 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277
fmalitac08d53e2015-11-17 09:53:29 -08001278 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1279
robertphillips@google.com466310d2013-12-03 16:43:54 +00001280 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001281
bsalomon78d58d12016-05-27 09:17:04 -07001282 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001283}
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1286 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001287 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001288 }
1289}
1290
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1292 bool forceMoveTo) {
1293 if (oval.width() < 0 || oval.height() < 0) {
1294 return;
1295 }
1296
reedf90ea012015-01-29 12:03:58 -08001297 if (fPathRef->countVerbs() == 0) {
1298 forceMoveTo = true;
1299 }
1300
1301 SkPoint lonePt;
1302 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1303 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1304 return;
1305 }
1306
reedd5d27d92015-02-09 13:54:43 -08001307 SkVector startV, stopV;
1308 SkRotationDirection dir;
1309 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1310
reed9e779d42015-02-17 11:43:14 -08001311 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001312
1313 // At this point, we know that the arc is not a lone point, but startV == stopV
1314 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1315 // cannot handle it.
1316 if (startV == stopV) {
1317 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1318 SkScalar radiusX = oval.width() / 2;
1319 SkScalar radiusY = oval.height() / 2;
1320 // We cannot use SkScalarSinCos function in the next line because
1321 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1322 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001323 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001324 // make sin(endAngle) to be 0 which will then draw a dot.
1325 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1326 oval.centerY() + radiusY * sk_float_sin(endAngle));
1327 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1328 return;
1329 }
1330
reedd5d27d92015-02-09 13:54:43 -08001331 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001332 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001333 if (count) {
1334 this->incReserve(count * 2 + 1);
1335 const SkPoint& pt = conics[0].fPts[0];
1336 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1337 for (int i = 0; i < count; ++i) {
1338 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1339 }
reed9e779d42015-02-17 11:43:14 -08001340 } else {
1341 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001342 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001343}
1344
caryclark55d49052016-01-23 05:07:04 -08001345// This converts the SVG arc to conics.
1346// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1347// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1348// See also SVG implementation notes:
1349// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1350// Note that arcSweep bool value is flipped from the original implementation.
1351void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1352 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001353 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001354 SkPoint srcPts[2];
1355 this->getLastPt(&srcPts[0]);
1356 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1357 // joining the endpoints.
1358 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1359 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001360 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001361 return;
1362 }
1363 // If the current point and target point for the arc are identical, it should be treated as a
1364 // zero length path. This ensures continuity in animations.
1365 srcPts[1].set(x, y);
1366 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001367 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001368 return;
1369 }
1370 rx = SkScalarAbs(rx);
1371 ry = SkScalarAbs(ry);
1372 SkVector midPointDistance = srcPts[0] - srcPts[1];
1373 midPointDistance *= 0.5f;
1374
1375 SkMatrix pointTransform;
1376 pointTransform.setRotate(-angle);
1377
1378 SkPoint transformedMidPoint;
1379 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1380 SkScalar squareRx = rx * rx;
1381 SkScalar squareRy = ry * ry;
1382 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1383 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1384
1385 // Check if the radii are big enough to draw the arc, scale radii if not.
1386 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1387 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1388 if (radiiScale > 1) {
1389 radiiScale = SkScalarSqrt(radiiScale);
1390 rx *= radiiScale;
1391 ry *= radiiScale;
1392 }
1393
1394 pointTransform.setScale(1 / rx, 1 / ry);
1395 pointTransform.preRotate(-angle);
1396
1397 SkPoint unitPts[2];
1398 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1399 SkVector delta = unitPts[1] - unitPts[0];
1400
1401 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1402 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1403
1404 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1405 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1406 scaleFactor = -scaleFactor;
1407 }
1408 delta.scale(scaleFactor);
1409 SkPoint centerPoint = unitPts[0] + unitPts[1];
1410 centerPoint *= 0.5f;
1411 centerPoint.offset(-delta.fY, delta.fX);
1412 unitPts[0] -= centerPoint;
1413 unitPts[1] -= centerPoint;
1414 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1415 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1416 SkScalar thetaArc = theta2 - theta1;
1417 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1418 thetaArc += SK_ScalarPI * 2;
1419 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1420 thetaArc -= SK_ScalarPI * 2;
1421 }
1422 pointTransform.setRotate(angle);
1423 pointTransform.preScale(rx, ry);
1424
Cary Clark1acd3c72017-12-08 11:37:01 -05001425#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001426 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001427#else
1428 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1429 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1430#endif
caryclark55d49052016-01-23 05:07:04 -08001431 SkScalar thetaWidth = thetaArc / segments;
1432 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1433 if (!SkScalarIsFinite(t)) {
1434 return;
1435 }
1436 SkScalar startTheta = theta1;
1437 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001438#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1439 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1440 return scalar == SkScalarFloorToScalar(scalar);
1441 };
1442 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1443 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1444 scalar_is_integer(x) && scalar_is_integer(y);
1445#endif
caryclark55d49052016-01-23 05:07:04 -08001446 for (int i = 0; i < segments; ++i) {
1447 SkScalar endTheta = startTheta + thetaWidth;
1448 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1449
1450 unitPts[1].set(cosEndTheta, sinEndTheta);
1451 unitPts[1] += centerPoint;
1452 unitPts[0] = unitPts[1];
1453 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1454 SkPoint mapped[2];
1455 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001456 /*
1457 Computing the arc width introduces rounding errors that cause arcs to start
1458 outside their marks. A round rect may lose convexity as a result. If the input
1459 values are on integers, place the conic on integers as well.
1460 */
1461#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1462 if (expectIntegers) {
1463 SkScalar* mappedScalars = &mapped[0].fX;
1464 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1465 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1466 }
1467 }
1468#endif
caryclark55d49052016-01-23 05:07:04 -08001469 this->conicTo(mapped[0], mapped[1], w);
1470 startTheta = endTheta;
1471 }
1472}
1473
1474void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1475 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1476 SkPoint currentPoint;
1477 this->getLastPt(&currentPoint);
1478 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1479}
1480
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001481void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 if (oval.isEmpty() || 0 == sweepAngle) {
1483 return;
1484 }
1485
1486 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1487
1488 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001489 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1490 // See SkPath::addOval() docs.
1491 SkScalar startOver90 = startAngle / 90.f;
1492 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1493 SkScalar error = startOver90 - startOver90I;
1494 if (SkScalarNearlyEqual(error, 0)) {
1495 // Index 1 is at startAngle == 0.
1496 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1497 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1498 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1499 (unsigned) startIndex);
1500 return;
1501 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502 }
bsalomon1978ce22016-05-31 10:42:16 -07001503 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504}
1505
1506/*
1507 Need to handle the case when the angle is sharp, and our computed end-points
1508 for the arc go behind pt1 and/or p2...
1509*/
reedc7789042015-01-29 12:59:11 -08001510void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001511 if (radius == 0) {
1512 this->lineTo(x1, y1);
1513 return;
1514 }
1515
1516 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001517
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 // need to know our prev pt so we can construct tangent vectors
1519 {
1520 SkPoint start;
1521 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001522 // Handle degenerate cases by adding a line to the first point and
1523 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 before.setNormalize(x1 - start.fX, y1 - start.fY);
1525 after.setNormalize(x2 - x1, y2 - y1);
1526 }
reed@google.comabf15c12011-01-18 20:35:51 +00001527
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 SkScalar cosh = SkPoint::DotProduct(before, after);
1529 SkScalar sinh = SkPoint::CrossProduct(before, after);
1530
1531 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001532 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 return;
1534 }
reed@google.comabf15c12011-01-18 20:35:51 +00001535
Mike Reeda99b6ce2017-02-04 11:04:26 -05001536 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537
Mike Reeda99b6ce2017-02-04 11:04:26 -05001538 SkScalar xx = x1 - dist * before.fX;
1539 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001540 after.setLength(dist);
1541 this->lineTo(xx, yy);
1542 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1543 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544}
1545
1546///////////////////////////////////////////////////////////////////////////////
1547
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001548void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 SkMatrix matrix;
1550
1551 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001552 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553}
1554
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001555void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001556 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001558 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 SkPoint pts[4];
1560 Verb verb;
1561
Cary Clark9480d822017-10-19 18:01:13 -04001562 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001563 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 while ((verb = iter.next(pts)) != kDone_Verb) {
1565 switch (verb) {
1566 case kMove_Verb:
1567 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001568 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1569 injectMoveToIfNeeded(); // In case last contour is closed
1570 this->lineTo(pts[0]);
1571 } else {
1572 this->moveTo(pts[0]);
1573 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001574 break;
1575 case kLine_Verb:
1576 proc(matrix, &pts[1], &pts[1], 1);
1577 this->lineTo(pts[1]);
1578 break;
1579 case kQuad_Verb:
1580 proc(matrix, &pts[1], &pts[1], 2);
1581 this->quadTo(pts[1], pts[2]);
1582 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001583 case kConic_Verb:
1584 proc(matrix, &pts[1], &pts[1], 2);
1585 this->conicTo(pts[1], pts[2], iter.conicWeight());
1586 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 case kCubic_Verb:
1588 proc(matrix, &pts[1], &pts[1], 3);
1589 this->cubicTo(pts[1], pts[2], pts[3]);
1590 break;
1591 case kClose_Verb:
1592 this->close();
1593 break;
1594 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001595 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001597 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 }
1599}
1600
1601///////////////////////////////////////////////////////////////////////////////
1602
reed@google.com277c3f82013-05-31 15:17:50 +00001603static int pts_in_verb(unsigned verb) {
1604 static const uint8_t gPtsInVerb[] = {
1605 1, // kMove
1606 1, // kLine
1607 2, // kQuad
1608 2, // kConic
1609 3, // kCubic
1610 0, // kClose
1611 0 // kDone
1612 };
1613
1614 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1615 return gPtsInVerb[verb];
1616}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617
reed@android.com8a1c16f2008-12-17 15:59:43 +00001618// ignore the last point of the 1st contour
1619void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001620 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1621 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001622 return;
1623 }
caryclark51c56782016-11-07 05:09:28 -08001624 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1625 SkASSERT(verbsEnd[0] == kMove_Verb);
1626 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1627 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001628
caryclark51c56782016-11-07 05:09:28 -08001629 while (verbs < verbsEnd) {
1630 uint8_t v = *verbs++;
1631 pts -= pts_in_verb(v);
1632 switch (v) {
1633 case kMove_Verb:
1634 // if the path has multiple contours, stop after reversing the last
1635 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001637 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 break;
1639 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001640 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001642 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001643 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001644 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001646 this->cubicTo(pts[2], pts[1], pts[0]);
1647 break;
1648 case kClose_Verb:
1649 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 break;
1651 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001652 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 break;
1654 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 }
1656}
1657
reed@google.com63d73742012-01-10 15:33:12 +00001658void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001659 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001660
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001661 const SkPoint* pts = src.fPathRef->pointsEnd();
1662 // we will iterator through src's verbs backwards
1663 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1664 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001665 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001666
1667 bool needMove = true;
1668 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001669 while (verbs < verbsEnd) {
1670 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001671 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001672
1673 if (needMove) {
1674 --pts;
1675 this->moveTo(pts->fX, pts->fY);
1676 needMove = false;
1677 }
1678 pts -= n;
1679 switch (v) {
1680 case kMove_Verb:
1681 if (needClose) {
1682 this->close();
1683 needClose = false;
1684 }
1685 needMove = true;
1686 pts += 1; // so we see the point in "if (needMove)" above
1687 break;
1688 case kLine_Verb:
1689 this->lineTo(pts[0]);
1690 break;
1691 case kQuad_Verb:
1692 this->quadTo(pts[1], pts[0]);
1693 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001694 case kConic_Verb:
1695 this->conicTo(pts[1], pts[0], *--conicWeights);
1696 break;
reed@google.com63d73742012-01-10 15:33:12 +00001697 case kCubic_Verb:
1698 this->cubicTo(pts[2], pts[1], pts[0]);
1699 break;
1700 case kClose_Verb:
1701 needClose = true;
1702 break;
1703 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001704 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001705 }
1706 }
1707}
1708
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709///////////////////////////////////////////////////////////////////////////////
1710
1711void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1712 SkMatrix matrix;
1713
1714 matrix.setTranslate(dx, dy);
1715 this->transform(matrix, dst);
1716}
1717
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1719 int level = 2) {
1720 if (--level >= 0) {
1721 SkPoint tmp[7];
1722
1723 SkChopCubicAtHalf(pts, tmp);
1724 subdivide_cubic_to(path, &tmp[0], level);
1725 subdivide_cubic_to(path, &tmp[3], level);
1726 } else {
1727 path->cubicTo(pts[1], pts[2], pts[3]);
1728 }
1729}
1730
1731void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1732 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001733 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001734 dst = (SkPath*)this;
1735 }
1736
tomhudson@google.com8d430182011-06-06 19:11:19 +00001737 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 SkPath tmp;
1739 tmp.fFillType = fFillType;
1740
1741 SkPath::Iter iter(*this, false);
1742 SkPoint pts[4];
1743 SkPath::Verb verb;
1744
reed@google.com4a3b7142012-05-16 17:16:46 +00001745 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001746 switch (verb) {
1747 case kMove_Verb:
1748 tmp.moveTo(pts[0]);
1749 break;
1750 case kLine_Verb:
1751 tmp.lineTo(pts[1]);
1752 break;
1753 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001754 // promote the quad to a conic
1755 tmp.conicTo(pts[1], pts[2],
1756 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001757 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001758 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001759 tmp.conicTo(pts[1], pts[2],
1760 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001761 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 case kCubic_Verb:
1763 subdivide_cubic_to(&tmp, pts);
1764 break;
1765 case kClose_Verb:
1766 tmp.close();
1767 break;
1768 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001769 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 break;
1771 }
1772 }
1773
1774 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001775 SkPathRef::Editor ed(&dst->fPathRef);
1776 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001777 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001779 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1780
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001783 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001784 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001785 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001786
reed026beb52015-06-10 14:23:15 -07001787 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1788 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001789 } else {
1790 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001791 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1792 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001793 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001794 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1795 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001796 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001797 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001798 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001799 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001800 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001801 }
1802 }
1803
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 SkDEBUGCODE(dst->validate();)
1805 }
1806}
1807
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808///////////////////////////////////////////////////////////////////////////////
1809///////////////////////////////////////////////////////////////////////////////
1810
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001812 kEmptyContour_SegmentState, // The current contour is empty. We may be
1813 // starting processing or we may have just
1814 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1816 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1817 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818};
1819
1820SkPath::Iter::Iter() {
1821#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001822 fPts = nullptr;
1823 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001826 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827#endif
1828 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001829 fVerbs = nullptr;
1830 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001831 fNeedClose = false;
1832}
1833
1834SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1835 this->setPath(path, forceClose);
1836}
1837
1838void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001839 fPts = path.fPathRef->points();
1840 fVerbs = path.fPathRef->verbs();
1841 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001842 fConicWeights = path.fPathRef->conicWeights();
1843 if (fConicWeights) {
1844 fConicWeights -= 1; // begin one behind
1845 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001847 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001848 fForceClose = SkToU8(forceClose);
1849 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001850 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851}
1852
1853bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001854 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001855 return false;
1856 }
1857 if (fForceClose) {
1858 return true;
1859 }
1860
1861 const uint8_t* verbs = fVerbs;
1862 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001863
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001864 if (kMove_Verb == *(verbs - 1)) {
1865 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001866 }
1867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001868 while (verbs > stop) {
1869 // verbs points one beyond the current verb, decrement first.
1870 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001871 if (kMove_Verb == v) {
1872 break;
1873 }
1874 if (kClose_Verb == v) {
1875 return true;
1876 }
1877 }
1878 return false;
1879}
1880
1881SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001882 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001883 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001884 // A special case: if both points are NaN, SkPoint::operation== returns
1885 // false, but the iterator expects that they are treated as the same.
1886 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001887 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1888 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001889 return kClose_Verb;
1890 }
1891
reed@google.com9e25dbf2012-05-15 17:05:38 +00001892 pts[0] = fLastPt;
1893 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 fLastPt = fMoveTo;
1895 fCloseLine = true;
1896 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001897 } else {
1898 pts[0] = fMoveTo;
1899 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901}
1902
reed@google.com9e25dbf2012-05-15 17:05:38 +00001903const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 if (fSegmentState == kAfterMove_SegmentState) {
1905 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1910 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001911 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913}
1914
caryclarke8c56662015-07-14 11:19:26 -07001915void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 // We need to step over anything that will not move the current draw point
1917 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001918 const uint8_t* lastMoveVerb = nullptr;
1919 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001920 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001921 SkPoint lastPt = fLastPt;
1922 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001923 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 switch (verb) {
1925 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 // Keep a record of this most recent move
1927 lastMoveVerb = fVerbs;
1928 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001929 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001930 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001931 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 fPts++;
1933 break;
1934
1935 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001936 // A close when we are in a segment is always valid except when it
1937 // follows a move which follows a segment.
1938 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001939 return;
1940 }
1941 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001942 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 break;
1944
1945 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001946 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001947 if (lastMoveVerb) {
1948 fVerbs = lastMoveVerb;
1949 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001950 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 return;
1952 }
1953 return;
1954 }
1955 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001956 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 fPts++;
1958 break;
1959
reed@google.com277c3f82013-05-31 15:17:50 +00001960 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001962 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 if (lastMoveVerb) {
1964 fVerbs = lastMoveVerb;
1965 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001966 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967 return;
1968 }
1969 return;
1970 }
1971 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001973 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001974 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001975 break;
1976
1977 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001978 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 if (lastMoveVerb) {
1980 fVerbs = lastMoveVerb;
1981 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001982 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 return;
1984 }
1985 return;
1986 }
1987 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001988 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001989 fPts += 3;
1990 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001991
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001993 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001994 }
1995 }
1996}
1997
reed@google.com4a3b7142012-05-16 17:16:46 +00001998SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001999 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002000
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002002 // Close the curve if requested and if there is some curve to close
2003 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002004 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002005 return kLine_Verb;
2006 }
2007 fNeedClose = false;
2008 return kClose_Verb;
2009 }
2010 return kDone_Verb;
2011 }
2012
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002013 // fVerbs is one beyond the current verb, decrement first
2014 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002015 const SkPoint* SK_RESTRICT srcPts = fPts;
2016 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002017
2018 switch (verb) {
2019 case kMove_Verb:
2020 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002021 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002022 verb = this->autoClose(pts);
2023 if (verb == kClose_Verb) {
2024 fNeedClose = false;
2025 }
2026 return (Verb)verb;
2027 }
2028 if (fVerbs == fVerbStop) { // might be a trailing moveto
2029 return kDone_Verb;
2030 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002031 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002032 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002034 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002035 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 fNeedClose = fForceClose;
2037 break;
2038 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002039 pts[0] = this->cons_moveTo();
2040 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002041 fLastPt = srcPts[0];
2042 fCloseLine = false;
2043 srcPts += 1;
2044 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002045 case kConic_Verb:
2046 fConicWeights += 1;
2047 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002049 pts[0] = this->cons_moveTo();
2050 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 fLastPt = srcPts[1];
2052 srcPts += 2;
2053 break;
2054 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002055 pts[0] = this->cons_moveTo();
2056 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 fLastPt = srcPts[2];
2058 srcPts += 3;
2059 break;
2060 case kClose_Verb:
2061 verb = this->autoClose(pts);
2062 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002063 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002064 } else {
2065 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002066 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002068 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069 break;
2070 }
2071 fPts = srcPts;
2072 return (Verb)verb;
2073}
2074
2075///////////////////////////////////////////////////////////////////////////////
2076
Ben Wagner4d1955c2017-03-10 13:08:15 -05002077#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002078#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002079#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002080
reed@google.com51bbe752013-01-17 22:07:50 +00002081static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002082 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002083 str->append(label);
2084 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002085
reed@google.com51bbe752013-01-17 22:07:50 +00002086 const SkScalar* values = &pts[0].fX;
2087 count *= 2;
2088
2089 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002090 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002091 if (i < count - 1) {
2092 str->append(", ");
2093 }
2094 }
Mike Reed405b9d22018-01-09 15:11:08 -05002095 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002096 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002097 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002098 }
caryclark08fa28c2014-10-23 13:08:56 -07002099 str->append(");");
reede05fed02014-12-15 07:59:53 -08002100 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002101 str->append(" // ");
2102 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002103 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002104 if (i < count - 1) {
2105 str->append(", ");
2106 }
2107 }
2108 if (conicWeight >= 0) {
2109 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002110 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002111 }
2112 }
2113 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002114}
2115
caryclarke9562592014-09-15 09:26:09 -07002116void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002117 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 Iter iter(*this, forceClose);
2119 SkPoint pts[4];
2120 Verb verb;
2121
reed@google.com51bbe752013-01-17 22:07:50 +00002122 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002123 char const * const gFillTypeStrs[] = {
2124 "Winding",
2125 "EvenOdd",
2126 "InverseWinding",
2127 "InverseEvenOdd",
2128 };
2129 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2130 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002131 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 switch (verb) {
2133 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002134 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
2136 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002137 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 break;
2139 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002140 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002142 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002143 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002144 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002146 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 break;
2148 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002149 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
2151 default:
2152 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2153 verb = kDone_Verb; // stop the loop
2154 break;
2155 }
caryclark1049f122015-04-20 08:31:59 -07002156 if (!wStream && builder.size()) {
2157 SkDebugf("%s", builder.c_str());
2158 builder.reset();
2159 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002160 }
caryclark66a5d8b2014-06-24 08:30:15 -07002161 if (wStream) {
2162 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002163 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002164}
2165
reed@android.come522ca52009-11-23 20:10:41 +00002166void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002167 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002168}
2169
2170void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002171 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002172}
2173
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002174
Cary Clark0461e9f2017-08-25 15:13:38 -04002175bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002176 if ((fFillType & ~3) != 0) {
2177 return false;
2178 }
reed@google.comabf15c12011-01-18 20:35:51 +00002179
djsollen@google.com077348c2012-10-22 20:23:32 +00002180#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002181 if (!fBoundsIsDirty) {
2182 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002183
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002184 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002185 if (SkToBool(fIsFinite) != isFinite) {
2186 return false;
2187 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002188
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002189 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002190 // if we're empty, fBounds may be empty but translated, so we can't
2191 // necessarily compare to bounds directly
2192 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2193 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002194 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2195 return false;
2196 }
reed@android.come522ca52009-11-23 20:10:41 +00002197 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002198 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002199 if (!fBounds.isEmpty()) {
2200 return false;
2201 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002202 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002203 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002204 if (!fBounds.contains(bounds)) {
2205 return false;
2206 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002207 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002208 }
reed@android.come522ca52009-11-23 20:10:41 +00002209 }
2210 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002211#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002212 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002213}
reed@android.come522ca52009-11-23 20:10:41 +00002214
reed@google.com04863fa2011-05-15 04:08:24 +00002215///////////////////////////////////////////////////////////////////////////////
2216
reed@google.com0b7b9822011-05-16 12:29:27 +00002217static int sign(SkScalar x) { return x < 0; }
2218#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002219
robertphillipsc506e302014-09-16 09:43:31 -07002220enum DirChange {
2221 kLeft_DirChange,
2222 kRight_DirChange,
2223 kStraight_DirChange,
2224 kBackwards_DirChange,
2225
2226 kInvalid_DirChange
2227};
2228
2229
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002230static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002231 // The error epsilon was empirically derived; worse case round rects
2232 // with a mid point outset by 2x float epsilon in tests had an error
2233 // of 12.
2234 const int epsilon = 16;
2235 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2236 return false;
2237 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002238 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002239 int aBits = SkFloatAs2sCompliment(compA);
2240 int bBits = SkFloatAs2sCompliment(compB);
2241 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002242}
2243
caryclarkb4216502015-03-02 13:02:34 -08002244static bool approximately_zero_when_compared_to(double x, double y) {
2245 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002246}
2247
caryclarkb4216502015-03-02 13:02:34 -08002248
reed@google.com04863fa2011-05-15 04:08:24 +00002249// only valid for a single contour
2250struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002251 Convexicator()
2252 : fPtCount(0)
2253 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002254 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002255 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002256 , fIsCurve(false)
2257 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002258 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002259 // warnings
djsollen2f124632016-04-29 13:53:05 -07002260 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002261 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002262 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002263 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002264 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002265
2266 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002267 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002268 }
2269
2270 SkPath::Convexity getConvexity() const { return fConvexity; }
2271
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002272 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002273 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002274
reed@google.com04863fa2011-05-15 04:08:24 +00002275 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002276 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002277 return;
2278 }
2279
2280 if (0 == fPtCount) {
2281 fCurrPt = pt;
2282 ++fPtCount;
2283 } else {
2284 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002285 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002286 if (!SkScalarIsFinite(lengthSqd)) {
2287 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002288 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002289 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002290 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002291 fCurrPt = pt;
2292 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002293 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002294 } else {
2295 SkASSERT(fPtCount > 2);
2296 this->addVec(vec);
2297 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002298
reed@google.com85b6e392011-05-15 20:25:17 +00002299 int sx = sign(vec.fX);
2300 int sy = sign(vec.fY);
2301 fDx += (sx != fSx);
2302 fDy += (sy != fSy);
2303 fSx = sx;
2304 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002305
reed@google.com85b6e392011-05-15 20:25:17 +00002306 if (fDx > 3 || fDy > 3) {
2307 fConvexity = SkPath::kConcave_Convexity;
2308 }
reed@google.com04863fa2011-05-15 04:08:24 +00002309 }
2310 }
2311 }
2312
2313 void close() {
2314 if (fPtCount > 2) {
2315 this->addVec(fFirstVec);
2316 }
2317 }
2318
caryclarkb4216502015-03-02 13:02:34 -08002319 DirChange directionChange(const SkVector& curVec) {
2320 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2321
2322 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2323 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2324 largest = SkTMax(largest, -smallest);
2325
2326 if (!almost_equal(largest, largest + cross)) {
2327 int sign = SkScalarSignAsInt(cross);
2328 if (sign) {
2329 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2330 }
2331 }
2332
2333 if (cross) {
2334 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2335 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2336 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2337 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2338 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2339 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2340 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2341 if (sign) {
2342 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2343 }
2344 }
2345 }
2346
Cary Clarkdf429f32017-11-08 11:44:31 -05002347 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2348 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2349 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2350 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002351 fLastVec.dot(curVec) < 0.0f) {
2352 return kBackwards_DirChange;
2353 }
2354
2355 return kStraight_DirChange;
2356 }
2357
Cary Clarkc8323aa2017-08-25 08:04:43 -04002358 bool hasBackwards() const {
2359 return fBackwards;
2360 }
caryclarkb4216502015-03-02 13:02:34 -08002361
caryclarkd3d1a982014-12-08 04:57:38 -08002362 bool isFinite() const {
2363 return fIsFinite;
2364 }
2365
caryclark5ccef572015-03-02 10:07:56 -08002366 void setCurve(bool isCurve) {
2367 fIsCurve = isCurve;
2368 }
2369
reed@google.com04863fa2011-05-15 04:08:24 +00002370private:
2371 void addVec(const SkVector& vec) {
2372 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002373 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002374 switch (dir) {
2375 case kLeft_DirChange: // fall through
2376 case kRight_DirChange:
2377 if (kInvalid_DirChange == fExpectedDir) {
2378 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002379 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2380 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002381 } else if (dir != fExpectedDir) {
2382 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002383 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002384 }
2385 fLastVec = vec;
2386 break;
2387 case kStraight_DirChange:
2388 break;
2389 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002390 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002391 // If any of the subsequent dir is non-backward, it'll be concave.
2392 // Otherwise, it's still convex.
2393 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002394 }
robertphillipsc506e302014-09-16 09:43:31 -07002395 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002396 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002397 break;
2398 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002399 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002400 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002401 }
2402 }
2403
caryclarkb4216502015-03-02 13:02:34 -08002404 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002405 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002406 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002407 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2408 // value with the current vec is deemed to be of a significant value.
2409 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002411 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002412 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002413 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002414 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002415 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002416 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002417 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002418};
2419
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002420SkPath::Convexity SkPath::internalGetConvexity() const {
2421 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002422 SkPoint pts[4];
2423 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002424 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002425
2426 int contourCount = 0;
2427 int count;
2428 Convexicator state;
2429
caryclarkd3d1a982014-12-08 04:57:38 -08002430 if (!isFinite()) {
2431 return kUnknown_Convexity;
2432 }
Brian Osman205a1262017-09-18 13:13:48 +00002433 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002434 switch (verb) {
2435 case kMove_Verb:
2436 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002437 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002438 return kConcave_Convexity;
2439 }
2440 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002441 // fall through
2442 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002443 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002444 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002445 break;
caryclark5ccef572015-03-02 10:07:56 -08002446 case kQuad_Verb:
2447 // fall through
2448 case kConic_Verb:
2449 // fall through
2450 case kCubic_Verb:
2451 count = 2 + (kCubic_Verb == verb);
2452 // As an additional enhancement, this could set curve true only
2453 // if the curve is nonlinear
2454 state.setCurve(true);
2455 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002456 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002457 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002458 state.close();
2459 count = 0;
2460 break;
2461 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002462 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002463 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002464 return kConcave_Convexity;
2465 }
2466
2467 for (int i = 1; i <= count; i++) {
2468 state.addPt(pts[i]);
2469 }
2470 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002471 if (!state.isFinite()) {
2472 return kUnknown_Convexity;
2473 }
reed@google.com04863fa2011-05-15 04:08:24 +00002474 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002475 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002476 return kConcave_Convexity;
2477 }
2478 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002479 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002480 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002481 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2482 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2483 fConvexity = Convexity::kConcave_Convexity;
2484 } else {
2485 fFirstDirection = state.getFirstDirection();
2486 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002487 }
2488 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002489}
reed@google.com69a99432012-01-10 18:00:10 +00002490
2491///////////////////////////////////////////////////////////////////////////////
2492
2493class ContourIter {
2494public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002495 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002496
2497 bool done() const { return fDone; }
2498 // if !done() then these may be called
2499 int count() const { return fCurrPtCount; }
2500 const SkPoint* pts() const { return fCurrPt; }
2501 void next();
2502
2503private:
2504 int fCurrPtCount;
2505 const SkPoint* fCurrPt;
2506 const uint8_t* fCurrVerb;
2507 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002508 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002509 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002510 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002511};
2512
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002513ContourIter::ContourIter(const SkPathRef& pathRef) {
2514 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002515 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002516 fCurrPt = pathRef.points();
2517 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002518 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002519 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002520 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002521 this->next();
2522}
2523
2524void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002525 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002526 fDone = true;
2527 }
2528 if (fDone) {
2529 return;
2530 }
2531
2532 // skip pts of prev contour
2533 fCurrPt += fCurrPtCount;
2534
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002535 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002536 int ptCount = 1; // moveTo
2537 const uint8_t* verbs = fCurrVerb;
2538
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002539 for (--verbs; verbs > fStopVerbs; --verbs) {
2540 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002541 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002542 goto CONTOUR_END;
2543 case SkPath::kLine_Verb:
2544 ptCount += 1;
2545 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002546 case SkPath::kConic_Verb:
2547 fCurrConicWeight += 1;
2548 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002549 case SkPath::kQuad_Verb:
2550 ptCount += 2;
2551 break;
2552 case SkPath::kCubic_Verb:
2553 ptCount += 3;
2554 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002555 case SkPath::kClose_Verb:
2556 break;
2557 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002558 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002559 break;
2560 }
2561 }
2562CONTOUR_END:
2563 fCurrPtCount = ptCount;
2564 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002565 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002566}
2567
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002568// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002569static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002570 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2571 // We may get 0 when the above subtracts underflow. We expect this to be
2572 // very rare and lazily promote to double.
2573 if (0 == cross) {
2574 double p0x = SkScalarToDouble(p0.fX);
2575 double p0y = SkScalarToDouble(p0.fY);
2576
2577 double p1x = SkScalarToDouble(p1.fX);
2578 double p1y = SkScalarToDouble(p1.fY);
2579
2580 double p2x = SkScalarToDouble(p2.fX);
2581 double p2y = SkScalarToDouble(p2.fY);
2582
2583 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2584 (p1y - p0y) * (p2x - p0x));
2585
2586 }
2587 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002588}
2589
reed@google.comc1ea60a2012-01-31 15:15:36 +00002590// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002591static int find_max_y(const SkPoint pts[], int count) {
2592 SkASSERT(count > 0);
2593 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002594 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002595 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002596 SkScalar y = pts[i].fY;
2597 if (y > max) {
2598 max = y;
2599 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002600 }
2601 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002602 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002603}
2604
reed@google.comcabaf1d2012-01-11 21:03:05 +00002605static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2606 int i = index;
2607 for (;;) {
2608 i = (i + inc) % n;
2609 if (i == index) { // we wrapped around, so abort
2610 break;
2611 }
2612 if (pts[index] != pts[i]) { // found a different point, success!
2613 break;
2614 }
2615 }
2616 return i;
2617}
2618
reed@google.comc1ea60a2012-01-31 15:15:36 +00002619/**
2620 * Starting at index, and moving forward (incrementing), find the xmin and
2621 * xmax of the contiguous points that have the same Y.
2622 */
2623static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2624 int* maxIndexPtr) {
2625 const SkScalar y = pts[index].fY;
2626 SkScalar min = pts[index].fX;
2627 SkScalar max = min;
2628 int minIndex = index;
2629 int maxIndex = index;
2630 for (int i = index + 1; i < n; ++i) {
2631 if (pts[i].fY != y) {
2632 break;
2633 }
2634 SkScalar x = pts[i].fX;
2635 if (x < min) {
2636 min = x;
2637 minIndex = i;
2638 } else if (x > max) {
2639 max = x;
2640 maxIndex = i;
2641 }
2642 }
2643 *maxIndexPtr = maxIndex;
2644 return minIndex;
2645}
2646
reed026beb52015-06-10 14:23:15 -07002647static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2648 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002649}
2650
reed@google.comac8543f2012-01-30 20:51:25 +00002651/*
2652 * We loop through all contours, and keep the computed cross-product of the
2653 * contour that contained the global y-max. If we just look at the first
2654 * contour, we may find one that is wound the opposite way (correctly) since
2655 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2656 * that is outer most (or at least has the global y-max) before we can consider
2657 * its cross product.
2658 */
reed026beb52015-06-10 14:23:15 -07002659bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002660 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2661 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002662 return true;
2663 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002664
2665 // don't want to pay the cost for computing this if it
2666 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002667 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2668 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002669 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002670 return false;
2671 }
reed@google.com69a99432012-01-10 18:00:10 +00002672
reed026beb52015-06-10 14:23:15 -07002673 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002674
reed@google.comac8543f2012-01-30 20:51:25 +00002675 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002676 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002677 SkScalar ymaxCross = 0;
2678
reed@google.com69a99432012-01-10 18:00:10 +00002679 for (; !iter.done(); iter.next()) {
2680 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002681 if (n < 3) {
2682 continue;
2683 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002684
reed@google.comcabaf1d2012-01-11 21:03:05 +00002685 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002686 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002687 int index = find_max_y(pts, n);
2688 if (pts[index].fY < ymax) {
2689 continue;
2690 }
2691
2692 // If there is more than 1 distinct point at the y-max, we take the
2693 // x-min and x-max of them and just subtract to compute the dir.
2694 if (pts[(index + 1) % n].fY == pts[index].fY) {
2695 int maxIndex;
2696 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2697 if (minIndex == maxIndex) {
2698 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002699 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002700 SkASSERT(pts[minIndex].fY == pts[index].fY);
2701 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2702 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2703 // we just subtract the indices, and let that auto-convert to
2704 // SkScalar, since we just want - or + to signal the direction.
2705 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002706 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002707 TRY_CROSSPROD:
2708 // Find a next and prev index to use for the cross-product test,
2709 // but we try to find pts that form non-zero vectors from pts[index]
2710 //
2711 // Its possible that we can't find two non-degenerate vectors, so
2712 // we have to guard our search (e.g. all the pts could be in the
2713 // same place).
2714
2715 // we pass n - 1 instead of -1 so we don't foul up % operator by
2716 // passing it a negative LH argument.
2717 int prev = find_diff_pt(pts, index, n, n - 1);
2718 if (prev == index) {
2719 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002720 continue;
2721 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002722 int next = find_diff_pt(pts, index, n, 1);
2723 SkASSERT(next != index);
2724 cross = cross_prod(pts[prev], pts[index], pts[next]);
2725 // if we get a zero and the points are horizontal, then we look at the spread in
2726 // x-direction. We really should continue to walk away from the degeneracy until
2727 // there is a divergence.
2728 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2729 // construct the subtract so we get the correct Direction below
2730 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002731 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002732 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002733
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002734 if (cross) {
2735 // record our best guess so far
2736 ymax = pts[index].fY;
2737 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002738 }
2739 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002740 if (ymaxCross) {
2741 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002742 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002743 return true;
2744 } else {
2745 return false;
2746 }
reed@google.comac8543f2012-01-30 20:51:25 +00002747}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002748
2749///////////////////////////////////////////////////////////////////////////////
2750
caryclark9aacd902015-12-14 08:38:09 -08002751static bool between(SkScalar a, SkScalar b, SkScalar c) {
2752 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2753 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2754 return (a - b) * (c - b) <= 0;
2755}
2756
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002757static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2758 SkScalar t) {
2759 SkScalar A = c3 + 3*(c1 - c2) - c0;
2760 SkScalar B = 3*(c2 - c1 - c1 + c0);
2761 SkScalar C = 3*(c1 - c0);
2762 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002763 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002764}
2765
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002766template <size_t N> static void find_minmax(const SkPoint pts[],
2767 SkScalar* minPtr, SkScalar* maxPtr) {
2768 SkScalar min, max;
2769 min = max = pts[0].fX;
2770 for (size_t i = 1; i < N; ++i) {
2771 min = SkMinScalar(min, pts[i].fX);
2772 max = SkMaxScalar(max, pts[i].fX);
2773 }
2774 *minPtr = min;
2775 *maxPtr = max;
2776}
2777
caryclark9cb5d752015-12-18 04:35:24 -08002778static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2779 if (start.fY == end.fY) {
2780 return between(start.fX, x, end.fX) && x != end.fX;
2781 } else {
2782 return x == start.fX && y == start.fY;
2783 }
2784}
2785
caryclark9aacd902015-12-14 08:38:09 -08002786static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002787 SkScalar y0 = pts[0].fY;
2788 SkScalar y3 = pts[3].fY;
2789
2790 int dir = 1;
2791 if (y0 > y3) {
2792 SkTSwap(y0, y3);
2793 dir = -1;
2794 }
2795 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002796 return 0;
2797 }
caryclark9cb5d752015-12-18 04:35:24 -08002798 if (checkOnCurve(x, y, pts[0], pts[3])) {
2799 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002800 return 0;
2801 }
caryclark9cb5d752015-12-18 04:35:24 -08002802 if (y == y3) {
2803 return 0;
2804 }
2805
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002806 // quickreject or quickaccept
2807 SkScalar min, max;
2808 find_minmax<4>(pts, &min, &max);
2809 if (x < min) {
2810 return 0;
2811 }
2812 if (x > max) {
2813 return dir;
2814 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002815
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002816 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002817 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002818 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002819 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002820 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002821 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002822 if (SkScalarNearlyEqual(xt, x)) {
2823 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2824 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002825 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002826 }
2827 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 return xt < x ? dir : 0;
2829}
2830
caryclark9aacd902015-12-14 08:38:09 -08002831static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002832 SkPoint dst[10];
2833 int n = SkChopCubicAtYExtrema(pts, dst);
2834 int w = 0;
2835 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002836 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002837 }
2838 return w;
2839}
2840
caryclark9aacd902015-12-14 08:38:09 -08002841static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2842 SkASSERT(src);
2843 SkASSERT(t >= 0 && t <= 1);
2844 SkScalar src2w = src[2] * w;
2845 SkScalar C = src[0];
2846 SkScalar A = src[4] - 2 * src2w + C;
2847 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002848 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002849}
2850
2851
2852static double conic_eval_denominator(SkScalar w, SkScalar t) {
2853 SkScalar B = 2 * (w - 1);
2854 SkScalar C = 1;
2855 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002856 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002857}
2858
2859static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2860 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002861 SkScalar y0 = pts[0].fY;
2862 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002863
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 int dir = 1;
2865 if (y0 > y2) {
2866 SkTSwap(y0, y2);
2867 dir = -1;
2868 }
caryclark9aacd902015-12-14 08:38:09 -08002869 if (y < y0 || y > y2) {
2870 return 0;
2871 }
caryclark9cb5d752015-12-18 04:35:24 -08002872 if (checkOnCurve(x, y, pts[0], pts[2])) {
2873 *onCurveCount += 1;
2874 return 0;
2875 }
caryclark9aacd902015-12-14 08:38:09 -08002876 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002877 return 0;
2878 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
caryclark9aacd902015-12-14 08:38:09 -08002880 SkScalar roots[2];
2881 SkScalar A = pts[2].fY;
2882 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2883 SkScalar C = pts[0].fY;
2884 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2885 B -= C; // B = b*w - w * yCept + yCept - a
2886 C -= y;
2887 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2888 SkASSERT(n <= 1);
2889 SkScalar xt;
2890 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002891 // zero roots are returned only when y0 == y
2892 // Need [0] if dir == 1
2893 // and [2] if dir == -1
2894 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002895 } else {
2896 SkScalar t = roots[0];
2897 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2898 }
2899 if (SkScalarNearlyEqual(xt, x)) {
2900 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2901 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002902 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002903 }
2904 }
2905 return xt < x ? dir : 0;
2906}
2907
2908static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2909 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2910 if (y0 == y1) {
2911 return true;
2912 }
2913 if (y0 < y1) {
2914 return y1 <= y2;
2915 } else {
2916 return y1 >= y2;
2917 }
2918}
2919
2920static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2921 int* onCurveCount) {
2922 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002923 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002924 // If the data points are very large, the conic may not be monotonic but may also
2925 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002926 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2927 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2928 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002929 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2930 }
2931 return w;
2932}
2933
2934static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2935 SkScalar y0 = pts[0].fY;
2936 SkScalar y2 = pts[2].fY;
2937
2938 int dir = 1;
2939 if (y0 > y2) {
2940 SkTSwap(y0, y2);
2941 dir = -1;
2942 }
2943 if (y < y0 || y > y2) {
2944 return 0;
2945 }
caryclark9cb5d752015-12-18 04:35:24 -08002946 if (checkOnCurve(x, y, pts[0], pts[2])) {
2947 *onCurveCount += 1;
2948 return 0;
2949 }
caryclark9aacd902015-12-14 08:38:09 -08002950 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002951 return 0;
2952 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002953 // bounds check on X (not required. is it faster?)
2954#if 0
2955 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2956 return 0;
2957 }
2958#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002959
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002960 SkScalar roots[2];
2961 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2962 2 * (pts[1].fY - pts[0].fY),
2963 pts[0].fY - y,
2964 roots);
2965 SkASSERT(n <= 1);
2966 SkScalar xt;
2967 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002968 // zero roots are returned only when y0 == y
2969 // Need [0] if dir == 1
2970 // and [2] if dir == -1
2971 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 } else {
2973 SkScalar t = roots[0];
2974 SkScalar C = pts[0].fX;
2975 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2976 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002977 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002978 }
caryclark9aacd902015-12-14 08:38:09 -08002979 if (SkScalarNearlyEqual(xt, x)) {
2980 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2981 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002982 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002983 }
2984 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002985 return xt < x ? dir : 0;
2986}
2987
caryclark9aacd902015-12-14 08:38:09 -08002988static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 SkPoint dst[5];
2990 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002991
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2993 n = SkChopQuadAtYExtrema(pts, dst);
2994 pts = dst;
2995 }
caryclark9aacd902015-12-14 08:38:09 -08002996 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002998 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002999 }
3000 return w;
3001}
3002
caryclark9aacd902015-12-14 08:38:09 -08003003static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003004 SkScalar x0 = pts[0].fX;
3005 SkScalar y0 = pts[0].fY;
3006 SkScalar x1 = pts[1].fX;
3007 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003008
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003010
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 int dir = 1;
3012 if (y0 > y1) {
3013 SkTSwap(y0, y1);
3014 dir = -1;
3015 }
caryclark9aacd902015-12-14 08:38:09 -08003016 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003017 return 0;
3018 }
caryclark9cb5d752015-12-18 04:35:24 -08003019 if (checkOnCurve(x, y, pts[0], pts[1])) {
3020 *onCurveCount += 1;
3021 return 0;
3022 }
3023 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003024 return 0;
3025 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003026 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003027
caryclark9aacd902015-12-14 08:38:09 -08003028 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003029 // zero cross means the point is on the line, and since the case where
3030 // y of the query point is at the end point is handled above, we can be
3031 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003032 if (x != x1 || y != pts[1].fY) {
3033 *onCurveCount += 1;
3034 }
caryclark9aacd902015-12-14 08:38:09 -08003035 dir = 0;
3036 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003037 dir = 0;
3038 }
3039 return dir;
3040}
3041
caryclark9aacd902015-12-14 08:38:09 -08003042static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3043 SkTDArray<SkVector>* tangents) {
3044 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3045 && !between(pts[2].fY, y, pts[3].fY)) {
3046 return;
3047 }
3048 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3049 && !between(pts[2].fX, x, pts[3].fX)) {
3050 return;
3051 }
3052 SkPoint dst[10];
3053 int n = SkChopCubicAtYExtrema(pts, dst);
3054 for (int i = 0; i <= n; ++i) {
3055 SkPoint* c = &dst[i * 3];
3056 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003057 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003058 continue;
mbarbella276e6332016-05-31 14:44:01 -07003059 }
caryclark9aacd902015-12-14 08:38:09 -08003060 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3061 if (!SkScalarNearlyEqual(x, xt)) {
3062 continue;
3063 }
3064 SkVector tangent;
3065 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3066 tangents->push(tangent);
3067 }
3068}
3069
3070static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3071 SkTDArray<SkVector>* tangents) {
3072 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3073 return;
3074 }
3075 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3076 return;
3077 }
3078 SkScalar roots[2];
3079 SkScalar A = pts[2].fY;
3080 SkScalar B = pts[1].fY * w - y * w + y;
3081 SkScalar C = pts[0].fY;
3082 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3083 B -= C; // B = b*w - w * yCept + yCept - a
3084 C -= y;
3085 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3086 for (int index = 0; index < n; ++index) {
3087 SkScalar t = roots[index];
3088 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3089 if (!SkScalarNearlyEqual(x, xt)) {
3090 continue;
3091 }
3092 SkConic conic(pts, w);
3093 tangents->push(conic.evalTangentAt(t));
3094 }
3095}
3096
3097static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3098 SkTDArray<SkVector>* tangents) {
3099 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3100 return;
3101 }
3102 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3103 return;
3104 }
3105 SkScalar roots[2];
3106 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3107 2 * (pts[1].fY - pts[0].fY),
3108 pts[0].fY - y,
3109 roots);
3110 for (int index = 0; index < n; ++index) {
3111 SkScalar t = roots[index];
3112 SkScalar C = pts[0].fX;
3113 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3114 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003115 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003116 if (!SkScalarNearlyEqual(x, xt)) {
3117 continue;
3118 }
3119 tangents->push(SkEvalQuadTangentAt(pts, t));
3120 }
3121}
3122
3123static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3124 SkTDArray<SkVector>* tangents) {
3125 SkScalar y0 = pts[0].fY;
3126 SkScalar y1 = pts[1].fY;
3127 if (!between(y0, y, y1)) {
3128 return;
3129 }
3130 SkScalar x0 = pts[0].fX;
3131 SkScalar x1 = pts[1].fX;
3132 if (!between(x0, x, x1)) {
3133 return;
3134 }
3135 SkScalar dx = x1 - x0;
3136 SkScalar dy = y1 - y0;
3137 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3138 return;
3139 }
3140 SkVector v;
3141 v.set(dx, dy);
3142 tangents->push(v);
3143}
3144
reed@google.com4db592c2013-10-30 17:39:43 +00003145static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3146 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3147}
3148
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003149bool SkPath::contains(SkScalar x, SkScalar y) const {
3150 bool isInverse = this->isInverseFillType();
3151 if (this->isEmpty()) {
3152 return isInverse;
3153 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003154
reed@google.com4db592c2013-10-30 17:39:43 +00003155 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003156 return isInverse;
3157 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003158
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003159 SkPath::Iter iter(*this, true);
3160 bool done = false;
3161 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003162 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003163 do {
3164 SkPoint pts[4];
3165 switch (iter.next(pts, false)) {
3166 case SkPath::kMove_Verb:
3167 case SkPath::kClose_Verb:
3168 break;
3169 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003170 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 break;
3172 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003173 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003174 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003175 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003176 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003177 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003178 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003179 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003180 break;
3181 case SkPath::kDone_Verb:
3182 done = true;
3183 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003184 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003185 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003186 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3187 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3188 if (evenOddFill) {
3189 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003190 }
caryclark9aacd902015-12-14 08:38:09 -08003191 if (w) {
3192 return !isInverse;
3193 }
3194 if (onCurveCount <= 1) {
3195 return SkToBool(onCurveCount) ^ isInverse;
3196 }
3197 if ((onCurveCount & 1) || evenOddFill) {
3198 return SkToBool(onCurveCount & 1) ^ isInverse;
3199 }
halcanary9d524f22016-03-29 09:03:52 -07003200 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003201 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3202 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003203 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003204 SkTDArray<SkVector> tangents;
3205 do {
3206 SkPoint pts[4];
3207 int oldCount = tangents.count();
3208 switch (iter.next(pts, false)) {
3209 case SkPath::kMove_Verb:
3210 case SkPath::kClose_Verb:
3211 break;
3212 case SkPath::kLine_Verb:
3213 tangent_line(pts, x, y, &tangents);
3214 break;
3215 case SkPath::kQuad_Verb:
3216 tangent_quad(pts, x, y, &tangents);
3217 break;
3218 case SkPath::kConic_Verb:
3219 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3220 break;
3221 case SkPath::kCubic_Verb:
3222 tangent_cubic(pts, x, y, &tangents);
3223 break;
3224 case SkPath::kDone_Verb:
3225 done = true;
3226 break;
3227 }
3228 if (tangents.count() > oldCount) {
3229 int last = tangents.count() - 1;
3230 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003231 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003232 tangents.remove(last);
3233 } else {
3234 for (int index = 0; index < last; ++index) {
3235 const SkVector& test = tangents[index];
3236 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003237 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3238 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003239 tangents.remove(last);
3240 tangents.removeShuffle(index);
3241 break;
3242 }
3243 }
3244 }
3245 }
3246 } while (!done);
3247 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003248}
fmalitaaa0df4e2015-12-01 09:13:23 -08003249
3250int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3251 SkScalar w, SkPoint pts[], int pow2) {
3252 const SkConic conic(p0, p1, p2, w);
3253 return conic.chopIntoQuadsPOW2(pts, pow2);
3254}
bsalomonedc743a2016-06-01 09:42:31 -07003255
3256bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3257 unsigned* start) {
3258 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3259 return false;
3260 }
3261 SkPath::RawIter iter(path);
3262 SkPoint verbPts[4];
3263 SkPath::Verb v;
3264 SkPoint rectPts[5];
3265 int rectPtCnt = 0;
3266 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3267 switch (v) {
3268 case SkPath::kMove_Verb:
3269 if (0 != rectPtCnt) {
3270 return false;
3271 }
3272 rectPts[0] = verbPts[0];
3273 ++rectPtCnt;
3274 break;
3275 case SkPath::kLine_Verb:
3276 if (5 == rectPtCnt) {
3277 return false;
3278 }
3279 rectPts[rectPtCnt] = verbPts[1];
3280 ++rectPtCnt;
3281 break;
3282 case SkPath::kClose_Verb:
3283 if (4 == rectPtCnt) {
3284 rectPts[4] = rectPts[0];
3285 rectPtCnt = 5;
3286 }
3287 break;
3288 default:
3289 return false;
3290 }
3291 }
3292 if (rectPtCnt < 5) {
3293 return false;
3294 }
3295 if (rectPts[0] != rectPts[4]) {
3296 return false;
3297 }
bsalomon057ae8a2016-07-24 05:37:26 -07003298 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3299 // and pts 1 and 2 the opposite vertical or horizontal edge).
3300 bool vec03IsVertical;
3301 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3302 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3303 // Make sure it has non-zero width and height
3304 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003305 return false;
3306 }
bsalomon057ae8a2016-07-24 05:37:26 -07003307 vec03IsVertical = true;
3308 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3309 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3310 // Make sure it has non-zero width and height
3311 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3312 return false;
3313 }
3314 vec03IsVertical = false;
3315 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003316 return false;
3317 }
bsalomon057ae8a2016-07-24 05:37:26 -07003318 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3319 // set if it is on the bottom edge.
3320 unsigned sortFlags =
3321 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3322 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3323 switch (sortFlags) {
3324 case 0b00:
3325 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3326 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3327 *start = 0;
3328 break;
3329 case 0b01:
3330 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3331 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3332 *start = 1;
3333 break;
3334 case 0b10:
3335 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3336 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3337 *start = 3;
3338 break;
3339 case 0b11:
3340 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3341 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3342 *start = 2;
3343 break;
bsalomonedc743a2016-06-01 09:42:31 -07003344 }
bsalomonedc743a2016-06-01 09:42:31 -07003345 return true;
3346}
bsalomon21af9ca2016-08-25 12:29:23 -07003347
3348void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3349 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3350 SkASSERT(!oval.isEmpty());
3351 SkASSERT(sweepAngle);
3352
3353 path->reset();
3354 path->setIsVolatile(true);
3355 path->setFillType(SkPath::kWinding_FillType);
3356 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3357 path->addOval(oval);
3358 return;
3359 }
3360 if (useCenter) {
3361 path->moveTo(oval.centerX(), oval.centerY());
3362 }
3363 // Arc to mods at 360 and drawArc is not supposed to.
3364 bool forceMoveTo = !useCenter;
3365 while (sweepAngle <= -360.f) {
3366 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3367 startAngle -= 180.f;
3368 path->arcTo(oval, startAngle, -180.f, false);
3369 startAngle -= 180.f;
3370 forceMoveTo = false;
3371 sweepAngle += 360.f;
3372 }
3373 while (sweepAngle >= 360.f) {
3374 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3375 startAngle += 180.f;
3376 path->arcTo(oval, startAngle, 180.f, false);
3377 startAngle += 180.f;
3378 forceMoveTo = false;
3379 sweepAngle -= 360.f;
3380 }
3381 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3382 if (useCenter) {
3383 path->close();
3384 }
3385}
Mike Reed0d7dac82017-02-02 17:45:56 -08003386
3387///////////////////////////////////////////////////////////////////////////////////////////////////
3388#include "SkNx.h"
3389
3390static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3391 SkScalar ts[2];
3392 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3393 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3394 SkASSERT(n >= 0 && n <= 2);
3395 for (int i = 0; i < n; ++i) {
3396 extremas[i] = SkEvalQuadAt(src, ts[i]);
3397 }
3398 extremas[n] = src[2];
3399 return n + 1;
3400}
3401
3402static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3403 SkConic conic(src[0], src[1], src[2], w);
3404 SkScalar ts[2];
3405 int n = conic.findXExtrema(ts);
3406 n += conic.findYExtrema(&ts[n]);
3407 SkASSERT(n >= 0 && n <= 2);
3408 for (int i = 0; i < n; ++i) {
3409 extremas[i] = conic.evalAt(ts[i]);
3410 }
3411 extremas[n] = src[2];
3412 return n + 1;
3413}
3414
3415static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3416 SkScalar ts[4];
3417 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3418 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3419 SkASSERT(n >= 0 && n <= 4);
3420 for (int i = 0; i < n; ++i) {
3421 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3422 }
3423 extremas[n] = src[3];
3424 return n + 1;
3425}
3426
Mike Reed8d3196b2017-02-03 11:34:13 -05003427SkRect SkPath::computeTightBounds() const {
3428 if (0 == this->countVerbs()) {
3429 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003430 }
3431
Mike Reed8d3196b2017-02-03 11:34:13 -05003432 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3433 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003434 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003435
Mike Reed0d7dac82017-02-02 17:45:56 -08003436 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3437 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003438 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003439
3440 // initial with the first MoveTo, so we don't have to check inside the switch
3441 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003442 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003443 for (;;) {
3444 int count = 0;
3445 switch (iter.next(pts)) {
3446 case SkPath::kMove_Verb:
3447 extremas[0] = pts[0];
3448 count = 1;
3449 break;
3450 case SkPath::kLine_Verb:
3451 extremas[0] = pts[1];
3452 count = 1;
3453 break;
3454 case SkPath::kQuad_Verb:
3455 count = compute_quad_extremas(pts, extremas);
3456 break;
3457 case SkPath::kConic_Verb:
3458 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3459 break;
3460 case SkPath::kCubic_Verb:
3461 count = compute_cubic_extremas(pts, extremas);
3462 break;
3463 case SkPath::kClose_Verb:
3464 break;
3465 case SkPath::kDone_Verb:
3466 goto DONE;
3467 }
3468 for (int i = 0; i < count; ++i) {
3469 Sk2s tmp = from_point(extremas[i]);
3470 min = Sk2s::Min(min, tmp);
3471 max = Sk2s::Max(max, tmp);
3472 }
3473 }
3474DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003475 SkRect bounds;
3476 min.store((SkPoint*)&bounds.fLeft);
3477 max.store((SkPoint*)&bounds.fRight);
3478 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003479}
Cary Clarkdf429f32017-11-08 11:44:31 -05003480
3481bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3482 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3483}
3484
3485bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3486 const SkPoint& p3, bool exact) {
3487 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3488 SkPointPriv::EqualsWithinTolerance(p2, p3);
3489}
3490
3491bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3492 const SkPoint& p3, const SkPoint& p4, bool exact) {
3493 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3494 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3495 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3496 SkPointPriv::EqualsWithinTolerance(p3, p4);
3497}