blob: 57192e8f9e7eefc73f9278a423ff47344eb0fc94 [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,
Cary Clark5c715182018-04-09 16:07:11 -0400448 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000449 int corners = 0;
450 SkPoint first, last;
Cary Clark5c715182018-04-09 16:07:11 -0400451 const SkPoint* firstPt = nullptr;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000452 const SkPoint* pts = *ptsPtr;
halcanary96fcdcc2015-08-27 07:41:13 -0700453 const SkPoint* savePts = nullptr;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000454 first.set(0, 0);
455 last.set(0, 0);
456 int firstDirection = 0;
457 int lastDirection = 0;
458 int nextDirection = 0;
459 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700461 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000462 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000463 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700464 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
465 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000466 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000467 savePts = pts;
468 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000469 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700470 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 case kLine_Verb: {
472 SkScalar left = last.fX;
473 SkScalar top = last.fY;
474 SkScalar right = pts->fX;
475 SkScalar bottom = pts->fY;
476 ++pts;
477 if (left != right && top != bottom) {
478 return false; // diagonal
479 }
480 if (left == right && top == bottom) {
481 break; // single point on side OK
482 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000483 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 if (0 == corners) {
485 firstDirection = nextDirection;
486 first = last;
487 last = pts[-1];
488 corners = 1;
489 closedOrMoved = false;
490 break;
491 }
492 if (closedOrMoved) {
493 return false; // closed followed by a line
494 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000495 if (autoClose && nextDirection == firstDirection) {
496 break; // colinear with first
497 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000498 closedOrMoved = autoClose;
499 if (lastDirection != nextDirection) {
500 if (++corners > 4) {
501 return false; // too many direction changes
502 }
503 }
504 last = pts[-1];
505 if (lastDirection == nextDirection) {
506 break; // colinear segment
507 }
508 // Possible values for corners are 2, 3, and 4.
509 // When corners == 3, nextDirection opposes firstDirection.
510 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000511 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000512 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
513 if ((directionCycle ^ turn) != nextDirection) {
514 return false; // direction didn't follow cycle
515 }
516 break;
517 }
518 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000519 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000520 case kCubic_Verb:
521 return false; // quadratic, cubic not allowed
522 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700523 if (allowPartial && !autoClose && firstDirection) {
524 insertClose = true;
525 *currVerb -= 1; // try move again afterwards
526 goto addMissingClose;
527 }
Cary Clark5c715182018-04-09 16:07:11 -0400528 firstPt = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000529 last = *pts++;
530 closedOrMoved = true;
531 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000532 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000533 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000534 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000535 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000536 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000537 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700538addMissingClose:
539 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000540 }
541 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000542 bool result = 4 == corners && (first == last || autoClose);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000543 if (!result) {
544 // check if we are just an incomplete rectangle, in which case we can
545 // return true, but not claim to be closed.
546 // e.g.
547 // 3 sided rectangle
548 // 4 sided but the last edge is not long enough to reach the start
549 //
550 SkScalar closeX = first.x() - last.x();
551 SkScalar closeY = first.y() - last.y();
552 if (closeX && closeY) {
553 return false; // we're diagonal, abort (can we ever reach this?)
554 }
555 int closeDirection = rect_make_dir(closeX, closeY);
556 // make sure the close-segment doesn't double-back on itself
557 if (3 == corners || (4 == corners && closeDirection == lastDirection)) {
558 result = true;
559 autoClose = false; // we are not closed
560 }
561 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000562 if (savePts) {
563 *ptsPtr = savePts;
564 }
Cary Clark5c715182018-04-09 16:07:11 -0400565 if (result && rect) {
566 ptrdiff_t count = (savePts ? savePts : pts) - firstPt;
567 rect->set(firstPt, (int) count);
568 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000569 if (result && isClosed) {
570 *isClosed = autoClose;
571 }
572 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000573 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000574 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000575 return result;
576}
577
robertphillips4f662e62014-12-29 14:06:51 -0800578bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000579 SkDEBUGCODE(this->validate();)
580 int currVerb = 0;
581 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400582 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000583}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000584
caryclark95bc5f32015-04-08 08:34:15 -0700585bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000586 SkDEBUGCODE(this->validate();)
587 int currVerb = 0;
588 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000589 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400590 SkRect testRects[2];
591 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000592 return false;
593 }
Cary Clark5c715182018-04-09 16:07:11 -0400594 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595 if (testRects[0].contains(testRects[1])) {
596 if (rects) {
597 rects[0] = testRects[0];
598 rects[1] = testRects[1];
599 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000600 if (dirs) {
601 dirs[0] = testDirs[0];
602 dirs[1] = testDirs[1];
603 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000604 return true;
605 }
606 if (testRects[1].contains(testRects[0])) {
607 if (rects) {
608 rects[0] = testRects[1];
609 rects[1] = testRects[0];
610 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000611 if (dirs) {
612 dirs[0] = testDirs[1];
613 dirs[1] = testDirs[0];
614 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000615 return true;
616 }
617 }
618 return false;
619}
620
Mike Reed0c3137c2018-02-20 13:57:05 -0500621bool SkPath::isOval(SkRect* bounds) const {
622 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
623}
624
625bool SkPath::isRRect(SkRRect* rrect) const {
626 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
627}
628
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000629int SkPath::countPoints() const {
630 return fPathRef->countPoints();
631}
632
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000633int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 SkDEBUGCODE(this->validate();)
635
636 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000637 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800639 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000640 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641}
642
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000643SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
645 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000646 }
647 return SkPoint::Make(0, 0);
648}
649
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650int SkPath::countVerbs() const {
651 return fPathRef->countVerbs();
652}
653
654static inline void copy_verbs_reverse(uint8_t* inorderDst,
655 const uint8_t* reversedSrc,
656 int count) {
657 for (int i = 0; i < count; ++i) {
658 inorderDst[i] = reversedSrc[~i];
659 }
660}
661
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000662int SkPath::getVerbs(uint8_t dst[], int max) const {
663 SkDEBUGCODE(this->validate();)
664
665 SkASSERT(max >= 0);
666 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000667 int count = SkMin32(max, fPathRef->countVerbs());
668 copy_verbs_reverse(dst, fPathRef->verbs(), count);
669 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000670}
671
reed@google.com294dd7b2011-10-11 11:58:32 +0000672bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000673 SkDEBUGCODE(this->validate();)
674
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000675 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000676 if (count > 0) {
677 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000678 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000679 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000680 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000682 if (lastPt) {
683 lastPt->set(0, 0);
684 }
685 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686}
687
caryclarkaec25102015-04-29 08:28:30 -0700688void SkPath::setPt(int index, SkScalar x, SkScalar y) {
689 SkDEBUGCODE(this->validate();)
690
691 int count = fPathRef->countPoints();
692 if (count <= index) {
693 return;
694 } else {
695 SkPathRef::Editor ed(&fPathRef);
696 ed.atPoint(index)->set(x, y);
697 }
698}
699
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700void SkPath::setLastPt(SkScalar x, SkScalar y) {
701 SkDEBUGCODE(this->validate();)
702
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000703 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 if (count == 0) {
705 this->moveTo(x, y);
706 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 SkPathRef::Editor ed(&fPathRef);
708 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709 }
710}
711
reed@google.com04863fa2011-05-15 04:08:24 +0000712void SkPath::setConvexity(Convexity c) {
713 if (fConvexity != c) {
714 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000715 }
716}
717
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718//////////////////////////////////////////////////////////////////////////////
719// Construction methods
720
reed026beb52015-06-10 14:23:15 -0700721#define DIRTY_AFTER_EDIT \
722 do { \
723 fConvexity = kUnknown_Convexity; \
724 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000725 } while (0)
726
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727void SkPath::incReserve(U16CPU inc) {
728 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000729 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000730 SkDEBUGCODE(this->validate();)
731}
732
733void SkPath::moveTo(SkScalar x, SkScalar y) {
734 SkDEBUGCODE(this->validate();)
735
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000736 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000738 // remember our index
739 fLastMoveToIndex = fPathRef->countPoints();
740
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000741 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700742
743 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000744}
745
746void SkPath::rMoveTo(SkScalar x, SkScalar y) {
747 SkPoint pt;
748 this->getLastPt(&pt);
749 this->moveTo(pt.fX + x, pt.fY + y);
750}
751
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000752void SkPath::injectMoveToIfNeeded() {
753 if (fLastMoveToIndex < 0) {
754 SkScalar x, y;
755 if (fPathRef->countVerbs() == 0) {
756 x = y = 0;
757 } else {
758 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
759 x = pt.fX;
760 y = pt.fY;
761 }
762 this->moveTo(x, y);
763 }
764}
765
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766void SkPath::lineTo(SkScalar x, SkScalar y) {
767 SkDEBUGCODE(this->validate();)
768
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000769 this->injectMoveToIfNeeded();
770
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000771 SkPathRef::Editor ed(&fPathRef);
772 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000773
reed@google.comb54455e2011-05-16 14:16:04 +0000774 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775}
776
777void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000778 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779 SkPoint pt;
780 this->getLastPt(&pt);
781 this->lineTo(pt.fX + x, pt.fY + y);
782}
783
784void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
785 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000786
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000787 this->injectMoveToIfNeeded();
788
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000789 SkPathRef::Editor ed(&fPathRef);
790 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000791 pts[0].set(x1, y1);
792 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000793
reed@google.comb54455e2011-05-16 14:16:04 +0000794 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795}
796
797void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000798 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799 SkPoint pt;
800 this->getLastPt(&pt);
801 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
802}
803
reed@google.com277c3f82013-05-31 15:17:50 +0000804void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
805 SkScalar w) {
806 // check for <= 0 or NaN with this test
807 if (!(w > 0)) {
808 this->lineTo(x2, y2);
809 } else if (!SkScalarIsFinite(w)) {
810 this->lineTo(x1, y1);
811 this->lineTo(x2, y2);
812 } else if (SK_Scalar1 == w) {
813 this->quadTo(x1, y1, x2, y2);
814 } else {
815 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000816
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000817 this->injectMoveToIfNeeded();
818
reed@google.com277c3f82013-05-31 15:17:50 +0000819 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000820 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000821 pts[0].set(x1, y1);
822 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000823
reed@google.com277c3f82013-05-31 15:17:50 +0000824 DIRTY_AFTER_EDIT;
825 }
826}
827
828void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
829 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000830 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000831 SkPoint pt;
832 this->getLastPt(&pt);
833 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
834}
835
reed@android.com8a1c16f2008-12-17 15:59:43 +0000836void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
837 SkScalar x3, SkScalar y3) {
838 SkDEBUGCODE(this->validate();)
839
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000840 this->injectMoveToIfNeeded();
841
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000842 SkPathRef::Editor ed(&fPathRef);
843 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000844 pts[0].set(x1, y1);
845 pts[1].set(x2, y2);
846 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847
reed@google.comb54455e2011-05-16 14:16:04 +0000848 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849}
850
851void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
852 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000853 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000854 SkPoint pt;
855 this->getLastPt(&pt);
856 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
857 pt.fX + x3, pt.fY + y3);
858}
859
860void SkPath::close() {
861 SkDEBUGCODE(this->validate();)
862
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000863 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000865 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000866 case kLine_Verb:
867 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000868 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000870 case kMove_Verb: {
871 SkPathRef::Editor ed(&fPathRef);
872 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 }
reed@google.com277c3f82013-05-31 15:17:50 +0000875 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000876 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000877 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000878 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000879 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000880 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881 }
882 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000883
884 // signal that we need a moveTo to follow us (unless we're done)
885#if 0
886 if (fLastMoveToIndex >= 0) {
887 fLastMoveToIndex = ~fLastMoveToIndex;
888 }
889#else
890 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
891#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000892}
893
894///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000895
fmalitac08d53e2015-11-17 09:53:29 -0800896namespace {
897
898template <unsigned N>
899class PointIterator {
900public:
901 PointIterator(SkPath::Direction dir, unsigned startIndex)
902 : fCurrent(startIndex % N)
903 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
904
905 const SkPoint& current() const {
906 SkASSERT(fCurrent < N);
907 return fPts[fCurrent];
908 }
909
910 const SkPoint& next() {
911 fCurrent = (fCurrent + fAdvance) % N;
912 return this->current();
913 }
914
915protected:
916 SkPoint fPts[N];
917
918private:
919 unsigned fCurrent;
920 unsigned fAdvance;
921};
922
923class RectPointIterator : public PointIterator<4> {
924public:
925 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
926 : PointIterator(dir, startIndex) {
927
928 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
929 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
930 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
931 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
932 }
933};
934
935class OvalPointIterator : public PointIterator<4> {
936public:
937 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
938 : PointIterator(dir, startIndex) {
939
940 const SkScalar cx = oval.centerX();
941 const SkScalar cy = oval.centerY();
942
943 fPts[0] = SkPoint::Make(cx, oval.fTop);
944 fPts[1] = SkPoint::Make(oval.fRight, cy);
945 fPts[2] = SkPoint::Make(cx, oval.fBottom);
946 fPts[3] = SkPoint::Make(oval.fLeft, cy);
947 }
948};
949
950class RRectPointIterator : public PointIterator<8> {
951public:
952 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
953 : PointIterator(dir, startIndex) {
954
955 const SkRect& bounds = rrect.getBounds();
956 const SkScalar L = bounds.fLeft;
957 const SkScalar T = bounds.fTop;
958 const SkScalar R = bounds.fRight;
959 const SkScalar B = bounds.fBottom;
960
961 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
962 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
963 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
964 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
965 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
966 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
967 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
968 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
969 }
970};
971
972} // anonymous namespace
973
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000974static void assert_known_direction(int dir) {
975 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
976}
977
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800979 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000980}
981
982void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
983 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800984 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
985}
986
987void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000988 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700989 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800990 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000991 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800992 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000993
fmalitac08d53e2015-11-17 09:53:29 -0800994 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +0000995
fmalitac08d53e2015-11-17 09:53:29 -0800996 const int kVerbs = 5; // moveTo + 3x lineTo + close
997 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000998
fmalitac08d53e2015-11-17 09:53:29 -0800999 RectPointIterator iter(rect, dir, startIndex);
1000
1001 this->moveTo(iter.current());
1002 this->lineTo(iter.next());
1003 this->lineTo(iter.next());
1004 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001005 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001006
1007 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008}
1009
reed@google.com744faba2012-05-29 19:54:52 +00001010void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1011 SkDEBUGCODE(this->validate();)
1012 if (count <= 0) {
1013 return;
1014 }
1015
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001016 fLastMoveToIndex = fPathRef->countPoints();
1017
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001018 // +close makes room for the extra kClose_Verb
1019 SkPathRef::Editor ed(&fPathRef, count+close, count);
1020
1021 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001022 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001023 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1024 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001025 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001026
reed@google.com744faba2012-05-29 19:54:52 +00001027 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001028 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001029 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001030 }
1031
reed@google.com744faba2012-05-29 19:54:52 +00001032 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001033 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001034}
1035
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001036#include "SkGeometry.h"
1037
reedf90ea012015-01-29 12:03:58 -08001038static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1039 SkPoint* pt) {
1040 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001041 // Chrome uses this path to move into and out of ovals. If not
1042 // treated as a special case the moves can distort the oval's
1043 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001044 pt->set(oval.fRight, oval.centerY());
1045 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001046 } else if (0 == oval.width() && 0 == oval.height()) {
1047 // Chrome will sometimes create 0 radius round rects. Having degenerate
1048 // quad segments in the path prevents the path from being recognized as
1049 // a rect.
1050 // TODO: optimizing the case where only one of width or height is zero
1051 // should also be considered. This case, however, doesn't seem to be
1052 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001053 pt->set(oval.fRight, oval.fTop);
1054 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055 }
reedf90ea012015-01-29 12:03:58 -08001056 return false;
1057}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001058
reedd5d27d92015-02-09 13:54:43 -08001059// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1060//
1061static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1062 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1063 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1064 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001065
1066 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001067 loss in radians-conversion and/or sin/cos, we may end up with coincident
1068 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1069 of drawing a nearly complete circle (good).
1070 e.g. canvas.drawArc(0, 359.99, ...)
1071 -vs- canvas.drawArc(0, 359.9, ...)
1072 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001073 */
reedd5d27d92015-02-09 13:54:43 -08001074 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001075 SkScalar sw = SkScalarAbs(sweepAngle);
1076 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1077 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1078 // make a guess at a tiny angle (in radians) to tweak by
1079 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1080 // not sure how much will be enough, so we use a loop
1081 do {
1082 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001083 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1084 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001085 }
1086 }
reedd5d27d92015-02-09 13:54:43 -08001087 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1088}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089
reed9e779d42015-02-17 11:43:14 -08001090/**
1091 * If this returns 0, then the caller should just line-to the singlePt, else it should
1092 * ignore singlePt and append the specified number of conics.
1093 */
reedd5d27d92015-02-09 13:54:43 -08001094static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001095 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1096 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001097 SkMatrix matrix;
1098
1099 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1100 matrix.postTranslate(oval.centerX(), oval.centerY());
1101
reed9e779d42015-02-17 11:43:14 -08001102 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1103 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001104 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001105 }
1106 return count;
reedd5d27d92015-02-09 13:54:43 -08001107}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001108
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001109void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001110 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001111 SkRRect rrect;
1112 rrect.setRectRadii(rect, (const SkVector*) radii);
1113 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001114}
1115
reed@google.com4ed0fb72012-12-12 20:48:18 +00001116void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001117 // legacy start indices: 6 (CW) and 7(CCW)
1118 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1119}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001120
fmalitac08d53e2015-11-17 09:53:29 -08001121void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1122 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001123
caryclarkda707bf2015-11-19 14:47:43 -08001124 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001125 const SkRect& bounds = rrect.getBounds();
1126
Brian Salomon0a241ce2017-12-15 11:31:05 -05001127 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001128 // degenerate(rect) => radii points are collapsing
1129 this->addRect(bounds, dir, (startIndex + 1) / 2);
1130 } else if (rrect.isOval()) {
1131 // degenerate(oval) => line points are collapsing
1132 this->addOval(bounds, dir, startIndex / 2);
1133 } else {
1134 fFirstDirection = this->hasOnlyMoveTos() ?
1135 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1136
1137 SkAutoPathBoundsUpdate apbu(this, bounds);
1138 SkAutoDisableDirectionCheck addc(this);
1139
1140 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1141 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1142 const SkScalar weight = SK_ScalarRoot2Over2;
1143
1144 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1145 const int kVerbs = startsWithConic
1146 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1147 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1148 this->incReserve(kVerbs);
1149
1150 RRectPointIterator rrectIter(rrect, dir, startIndex);
1151 // Corner iterator indices follow the collapsed radii model,
1152 // adjusted such that the start pt is "behind" the radii start pt.
1153 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1154 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1155
1156 this->moveTo(rrectIter.current());
1157 if (startsWithConic) {
1158 for (unsigned i = 0; i < 3; ++i) {
1159 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1160 this->lineTo(rrectIter.next());
1161 }
1162 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1163 // final lineTo handled by close().
1164 } else {
1165 for (unsigned i = 0; i < 4; ++i) {
1166 this->lineTo(rrectIter.next());
1167 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1168 }
1169 }
1170 this->close();
1171
caryclarkda707bf2015-11-19 14:47:43 -08001172 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001173 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001174
fmalitac08d53e2015-11-17 09:53:29 -08001175 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1176 }
1177
1178 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001179}
1180
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001181bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001182 int count = fPathRef->countVerbs();
1183 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1184 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001185 if (*verbs == kLine_Verb ||
1186 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001187 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001188 *verbs == kCubic_Verb) {
1189 return false;
1190 }
1191 ++verbs;
1192 }
1193 return true;
1194}
1195
Brian Osmana2318572017-07-10 15:09:26 -04001196bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1197 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001198 if (count < 2) {
1199 return true;
1200 }
Brian Osmana2318572017-07-10 15:09:26 -04001201 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001202 const SkPoint& first = *pts;
1203 for (int index = 1; index < count; ++index) {
1204 if (first != pts[index]) {
1205 return false;
1206 }
1207 }
1208 return true;
1209}
1210
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001211void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1212 Direction dir) {
1213 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001214
humper@google.com75e3ca12013-04-08 21:44:11 +00001215 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001216 return;
1217 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001218
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001219 SkRRect rrect;
1220 rrect.setRectXY(rect, rx, ry);
1221 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001222}
1223
reed@android.com8a1c16f2008-12-17 15:59:43 +00001224void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001225 // legacy start index: 1
1226 this->addOval(oval, dir, 1);
1227}
1228
1229void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001230 assert_known_direction(dir);
1231
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001232 /* If addOval() is called after previous moveTo(),
1233 this path is still marked as an oval. This is used to
1234 fit into WebKit's calling sequences.
1235 We can't simply check isEmpty() in this case, as additional
1236 moveTo() would mark the path non empty.
1237 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001238 bool isOval = hasOnlyMoveTos();
1239 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001240 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001241 } else {
reed026beb52015-06-10 14:23:15 -07001242 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001243 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001244
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001245 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 SkAutoPathBoundsUpdate apbu(this, oval);
1247
fmalitac08d53e2015-11-17 09:53:29 -08001248 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1249 const int kVerbs = 6; // moveTo + 4x conicTo + close
1250 this->incReserve(kVerbs);
1251
1252 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1253 // The corner iterator pts are tracking "behind" the oval/radii pts.
1254 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001255 const SkScalar weight = SK_ScalarRoot2Over2;
1256
fmalitac08d53e2015-11-17 09:53:29 -08001257 this->moveTo(ovalIter.current());
1258 for (unsigned i = 0; i < 4; ++i) {
1259 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001260 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262
fmalitac08d53e2015-11-17 09:53:29 -08001263 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1264
robertphillips@google.com466310d2013-12-03 16:43:54 +00001265 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001266
bsalomon78d58d12016-05-27 09:17:04 -07001267 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001268}
1269
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1271 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001272 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 }
1274}
1275
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1277 bool forceMoveTo) {
1278 if (oval.width() < 0 || oval.height() < 0) {
1279 return;
1280 }
1281
reedf90ea012015-01-29 12:03:58 -08001282 if (fPathRef->countVerbs() == 0) {
1283 forceMoveTo = true;
1284 }
1285
1286 SkPoint lonePt;
1287 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1288 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1289 return;
1290 }
1291
reedd5d27d92015-02-09 13:54:43 -08001292 SkVector startV, stopV;
1293 SkRotationDirection dir;
1294 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1295
reed9e779d42015-02-17 11:43:14 -08001296 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001297
1298 // At this point, we know that the arc is not a lone point, but startV == stopV
1299 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1300 // cannot handle it.
1301 if (startV == stopV) {
1302 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1303 SkScalar radiusX = oval.width() / 2;
1304 SkScalar radiusY = oval.height() / 2;
1305 // We cannot use SkScalarSinCos function in the next line because
1306 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1307 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001308 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001309 // make sin(endAngle) to be 0 which will then draw a dot.
1310 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1311 oval.centerY() + radiusY * sk_float_sin(endAngle));
1312 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1313 return;
1314 }
1315
reedd5d27d92015-02-09 13:54:43 -08001316 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001317 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001318 if (count) {
1319 this->incReserve(count * 2 + 1);
1320 const SkPoint& pt = conics[0].fPts[0];
1321 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1322 for (int i = 0; i < count; ++i) {
1323 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1324 }
reed9e779d42015-02-17 11:43:14 -08001325 } else {
1326 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001327 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328}
1329
caryclark55d49052016-01-23 05:07:04 -08001330// This converts the SVG arc to conics.
1331// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1332// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1333// See also SVG implementation notes:
1334// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1335// Note that arcSweep bool value is flipped from the original implementation.
1336void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1337 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001338 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001339 SkPoint srcPts[2];
1340 this->getLastPt(&srcPts[0]);
1341 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1342 // joining the endpoints.
1343 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1344 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001345 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001346 return;
1347 }
1348 // If the current point and target point for the arc are identical, it should be treated as a
1349 // zero length path. This ensures continuity in animations.
1350 srcPts[1].set(x, y);
1351 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001352 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001353 return;
1354 }
1355 rx = SkScalarAbs(rx);
1356 ry = SkScalarAbs(ry);
1357 SkVector midPointDistance = srcPts[0] - srcPts[1];
1358 midPointDistance *= 0.5f;
1359
1360 SkMatrix pointTransform;
1361 pointTransform.setRotate(-angle);
1362
1363 SkPoint transformedMidPoint;
1364 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1365 SkScalar squareRx = rx * rx;
1366 SkScalar squareRy = ry * ry;
1367 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1368 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1369
1370 // Check if the radii are big enough to draw the arc, scale radii if not.
1371 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1372 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1373 if (radiiScale > 1) {
1374 radiiScale = SkScalarSqrt(radiiScale);
1375 rx *= radiiScale;
1376 ry *= radiiScale;
1377 }
1378
1379 pointTransform.setScale(1 / rx, 1 / ry);
1380 pointTransform.preRotate(-angle);
1381
1382 SkPoint unitPts[2];
1383 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1384 SkVector delta = unitPts[1] - unitPts[0];
1385
1386 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1387 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1388
1389 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1390 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1391 scaleFactor = -scaleFactor;
1392 }
1393 delta.scale(scaleFactor);
1394 SkPoint centerPoint = unitPts[0] + unitPts[1];
1395 centerPoint *= 0.5f;
1396 centerPoint.offset(-delta.fY, delta.fX);
1397 unitPts[0] -= centerPoint;
1398 unitPts[1] -= centerPoint;
1399 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1400 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1401 SkScalar thetaArc = theta2 - theta1;
1402 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1403 thetaArc += SK_ScalarPI * 2;
1404 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1405 thetaArc -= SK_ScalarPI * 2;
1406 }
1407 pointTransform.setRotate(angle);
1408 pointTransform.preScale(rx, ry);
1409
Cary Clark1acd3c72017-12-08 11:37:01 -05001410#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001411 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001412#else
1413 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1414 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1415#endif
caryclark55d49052016-01-23 05:07:04 -08001416 SkScalar thetaWidth = thetaArc / segments;
1417 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1418 if (!SkScalarIsFinite(t)) {
1419 return;
1420 }
1421 SkScalar startTheta = theta1;
1422 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001423#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1424 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1425 return scalar == SkScalarFloorToScalar(scalar);
1426 };
1427 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1428 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1429 scalar_is_integer(x) && scalar_is_integer(y);
1430#endif
caryclark55d49052016-01-23 05:07:04 -08001431 for (int i = 0; i < segments; ++i) {
1432 SkScalar endTheta = startTheta + thetaWidth;
1433 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1434
1435 unitPts[1].set(cosEndTheta, sinEndTheta);
1436 unitPts[1] += centerPoint;
1437 unitPts[0] = unitPts[1];
1438 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1439 SkPoint mapped[2];
1440 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001441 /*
1442 Computing the arc width introduces rounding errors that cause arcs to start
1443 outside their marks. A round rect may lose convexity as a result. If the input
1444 values are on integers, place the conic on integers as well.
1445 */
1446#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1447 if (expectIntegers) {
1448 SkScalar* mappedScalars = &mapped[0].fX;
1449 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1450 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1451 }
1452 }
1453#endif
caryclark55d49052016-01-23 05:07:04 -08001454 this->conicTo(mapped[0], mapped[1], w);
1455 startTheta = endTheta;
1456 }
1457}
1458
1459void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1460 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1461 SkPoint currentPoint;
1462 this->getLastPt(&currentPoint);
1463 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1464}
1465
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001466void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 if (oval.isEmpty() || 0 == sweepAngle) {
1468 return;
1469 }
1470
1471 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1472
1473 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001474 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1475 // See SkPath::addOval() docs.
1476 SkScalar startOver90 = startAngle / 90.f;
1477 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1478 SkScalar error = startOver90 - startOver90I;
1479 if (SkScalarNearlyEqual(error, 0)) {
1480 // Index 1 is at startAngle == 0.
1481 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1482 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1483 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1484 (unsigned) startIndex);
1485 return;
1486 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 }
bsalomon1978ce22016-05-31 10:42:16 -07001488 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489}
1490
1491/*
1492 Need to handle the case when the angle is sharp, and our computed end-points
1493 for the arc go behind pt1 and/or p2...
1494*/
reedc7789042015-01-29 12:59:11 -08001495void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001496 if (radius == 0) {
1497 this->lineTo(x1, y1);
1498 return;
1499 }
1500
1501 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001502
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503 // need to know our prev pt so we can construct tangent vectors
1504 {
1505 SkPoint start;
1506 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001507 // Handle degenerate cases by adding a line to the first point and
1508 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 before.setNormalize(x1 - start.fX, y1 - start.fY);
1510 after.setNormalize(x2 - x1, y2 - y1);
1511 }
reed@google.comabf15c12011-01-18 20:35:51 +00001512
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 SkScalar cosh = SkPoint::DotProduct(before, after);
1514 SkScalar sinh = SkPoint::CrossProduct(before, after);
1515
1516 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001517 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 return;
1519 }
reed@google.comabf15c12011-01-18 20:35:51 +00001520
Mike Reeda99b6ce2017-02-04 11:04:26 -05001521 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522
Mike Reeda99b6ce2017-02-04 11:04:26 -05001523 SkScalar xx = x1 - dist * before.fX;
1524 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001525 after.setLength(dist);
1526 this->lineTo(xx, yy);
1527 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1528 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001529}
1530
1531///////////////////////////////////////////////////////////////////////////////
1532
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001533void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001534 SkMatrix matrix;
1535
1536 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001537 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538}
1539
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001540void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001541 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001543 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544 SkPoint pts[4];
1545 Verb verb;
1546
Cary Clark9480d822017-10-19 18:01:13 -04001547 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001548 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001549 while ((verb = iter.next(pts)) != kDone_Verb) {
1550 switch (verb) {
1551 case kMove_Verb:
1552 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001553 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1554 injectMoveToIfNeeded(); // In case last contour is closed
1555 this->lineTo(pts[0]);
1556 } else {
1557 this->moveTo(pts[0]);
1558 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001559 break;
1560 case kLine_Verb:
1561 proc(matrix, &pts[1], &pts[1], 1);
1562 this->lineTo(pts[1]);
1563 break;
1564 case kQuad_Verb:
1565 proc(matrix, &pts[1], &pts[1], 2);
1566 this->quadTo(pts[1], pts[2]);
1567 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001568 case kConic_Verb:
1569 proc(matrix, &pts[1], &pts[1], 2);
1570 this->conicTo(pts[1], pts[2], iter.conicWeight());
1571 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 case kCubic_Verb:
1573 proc(matrix, &pts[1], &pts[1], 3);
1574 this->cubicTo(pts[1], pts[2], pts[3]);
1575 break;
1576 case kClose_Verb:
1577 this->close();
1578 break;
1579 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001580 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001582 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 }
1584}
1585
1586///////////////////////////////////////////////////////////////////////////////
1587
reed@google.com277c3f82013-05-31 15:17:50 +00001588static int pts_in_verb(unsigned verb) {
1589 static const uint8_t gPtsInVerb[] = {
1590 1, // kMove
1591 1, // kLine
1592 2, // kQuad
1593 2, // kConic
1594 3, // kCubic
1595 0, // kClose
1596 0 // kDone
1597 };
1598
1599 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1600 return gPtsInVerb[verb];
1601}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001602
reed@android.com8a1c16f2008-12-17 15:59:43 +00001603// ignore the last point of the 1st contour
1604void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001605 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1606 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 return;
1608 }
caryclark51c56782016-11-07 05:09:28 -08001609 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1610 SkASSERT(verbsEnd[0] == kMove_Verb);
1611 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1612 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613
caryclark51c56782016-11-07 05:09:28 -08001614 while (verbs < verbsEnd) {
1615 uint8_t v = *verbs++;
1616 pts -= pts_in_verb(v);
1617 switch (v) {
1618 case kMove_Verb:
1619 // if the path has multiple contours, stop after reversing the last
1620 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001622 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001623 break;
1624 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001625 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001627 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001628 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001629 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001631 this->cubicTo(pts[2], pts[1], pts[0]);
1632 break;
1633 case kClose_Verb:
1634 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 break;
1636 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001637 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001638 break;
1639 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 }
1641}
1642
reed@google.com63d73742012-01-10 15:33:12 +00001643void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001644 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001645
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001646 const SkPoint* pts = src.fPathRef->pointsEnd();
1647 // we will iterator through src's verbs backwards
1648 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1649 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001650 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001651
1652 bool needMove = true;
1653 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001654 while (verbs < verbsEnd) {
1655 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001656 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001657
1658 if (needMove) {
1659 --pts;
1660 this->moveTo(pts->fX, pts->fY);
1661 needMove = false;
1662 }
1663 pts -= n;
1664 switch (v) {
1665 case kMove_Verb:
1666 if (needClose) {
1667 this->close();
1668 needClose = false;
1669 }
1670 needMove = true;
1671 pts += 1; // so we see the point in "if (needMove)" above
1672 break;
1673 case kLine_Verb:
1674 this->lineTo(pts[0]);
1675 break;
1676 case kQuad_Verb:
1677 this->quadTo(pts[1], pts[0]);
1678 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001679 case kConic_Verb:
1680 this->conicTo(pts[1], pts[0], *--conicWeights);
1681 break;
reed@google.com63d73742012-01-10 15:33:12 +00001682 case kCubic_Verb:
1683 this->cubicTo(pts[2], pts[1], pts[0]);
1684 break;
1685 case kClose_Verb:
1686 needClose = true;
1687 break;
1688 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001689 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001690 }
1691 }
1692}
1693
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694///////////////////////////////////////////////////////////////////////////////
1695
1696void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1697 SkMatrix matrix;
1698
1699 matrix.setTranslate(dx, dy);
1700 this->transform(matrix, dst);
1701}
1702
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1704 int level = 2) {
1705 if (--level >= 0) {
1706 SkPoint tmp[7];
1707
1708 SkChopCubicAtHalf(pts, tmp);
1709 subdivide_cubic_to(path, &tmp[0], level);
1710 subdivide_cubic_to(path, &tmp[3], level);
1711 } else {
1712 path->cubicTo(pts[1], pts[2], pts[3]);
1713 }
1714}
1715
1716void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1717 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001718 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 dst = (SkPath*)this;
1720 }
1721
tomhudson@google.com8d430182011-06-06 19:11:19 +00001722 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723 SkPath tmp;
1724 tmp.fFillType = fFillType;
1725
1726 SkPath::Iter iter(*this, false);
1727 SkPoint pts[4];
1728 SkPath::Verb verb;
1729
reed@google.com4a3b7142012-05-16 17:16:46 +00001730 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001731 switch (verb) {
1732 case kMove_Verb:
1733 tmp.moveTo(pts[0]);
1734 break;
1735 case kLine_Verb:
1736 tmp.lineTo(pts[1]);
1737 break;
1738 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001739 // promote the quad to a conic
1740 tmp.conicTo(pts[1], pts[2],
1741 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001742 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001743 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001744 tmp.conicTo(pts[1], pts[2],
1745 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001746 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 case kCubic_Verb:
1748 subdivide_cubic_to(&tmp, pts);
1749 break;
1750 case kClose_Verb:
1751 tmp.close();
1752 break;
1753 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001754 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 break;
1756 }
1757 }
1758
1759 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 SkPathRef::Editor ed(&dst->fPathRef);
1761 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001762 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1765
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001768 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001769 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001771
reed026beb52015-06-10 14:23:15 -07001772 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1773 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001774 } else {
1775 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001776 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1777 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001778 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001779 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1780 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001781 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001782 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001783 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001784 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001785 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001786 }
1787 }
1788
reed@android.com8a1c16f2008-12-17 15:59:43 +00001789 SkDEBUGCODE(dst->validate();)
1790 }
1791}
1792
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793///////////////////////////////////////////////////////////////////////////////
1794///////////////////////////////////////////////////////////////////////////////
1795
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001797 kEmptyContour_SegmentState, // The current contour is empty. We may be
1798 // starting processing or we may have just
1799 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001800 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1801 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1802 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803};
1804
1805SkPath::Iter::Iter() {
1806#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001807 fPts = nullptr;
1808 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001811 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812#endif
1813 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001814 fVerbs = nullptr;
1815 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 fNeedClose = false;
1817}
1818
1819SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1820 this->setPath(path, forceClose);
1821}
1822
1823void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001824 fPts = path.fPathRef->points();
1825 fVerbs = path.fPathRef->verbs();
1826 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001827 fConicWeights = path.fPathRef->conicWeights();
1828 if (fConicWeights) {
1829 fConicWeights -= 1; // begin one behind
1830 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001832 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 fForceClose = SkToU8(forceClose);
1834 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001835 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836}
1837
1838bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001839 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 return false;
1841 }
1842 if (fForceClose) {
1843 return true;
1844 }
1845
1846 const uint8_t* verbs = fVerbs;
1847 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001848
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001849 if (kMove_Verb == *(verbs - 1)) {
1850 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001851 }
1852
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001853 while (verbs > stop) {
1854 // verbs points one beyond the current verb, decrement first.
1855 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 if (kMove_Verb == v) {
1857 break;
1858 }
1859 if (kClose_Verb == v) {
1860 return true;
1861 }
1862 }
1863 return false;
1864}
1865
1866SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001867 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001869 // A special case: if both points are NaN, SkPoint::operation== returns
1870 // false, but the iterator expects that they are treated as the same.
1871 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001872 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1873 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001874 return kClose_Verb;
1875 }
1876
reed@google.com9e25dbf2012-05-15 17:05:38 +00001877 pts[0] = fLastPt;
1878 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 fLastPt = fMoveTo;
1880 fCloseLine = true;
1881 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001882 } else {
1883 pts[0] = fMoveTo;
1884 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886}
1887
reed@google.com9e25dbf2012-05-15 17:05:38 +00001888const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 if (fSegmentState == kAfterMove_SegmentState) {
1890 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001892 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1895 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001896 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001897 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898}
1899
caryclarke8c56662015-07-14 11:19:26 -07001900void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 // We need to step over anything that will not move the current draw point
1902 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001903 const uint8_t* lastMoveVerb = nullptr;
1904 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001905 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 SkPoint lastPt = fLastPt;
1907 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001908 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 switch (verb) {
1910 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 // Keep a record of this most recent move
1912 lastMoveVerb = fVerbs;
1913 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001914 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001915 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 fPts++;
1918 break;
1919
1920 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001921 // A close when we are in a segment is always valid except when it
1922 // follows a move which follows a segment.
1923 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 return;
1925 }
1926 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001927 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001928 break;
1929
1930 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001931 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001932 if (lastMoveVerb) {
1933 fVerbs = lastMoveVerb;
1934 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001935 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001936 return;
1937 }
1938 return;
1939 }
1940 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001941 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 fPts++;
1943 break;
1944
reed@google.com277c3f82013-05-31 15:17:50 +00001945 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001947 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 if (lastMoveVerb) {
1949 fVerbs = lastMoveVerb;
1950 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001951 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 return;
1953 }
1954 return;
1955 }
1956 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001957 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001959 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 break;
1961
1962 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001963 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 if (lastMoveVerb) {
1965 fVerbs = lastMoveVerb;
1966 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001967 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968 return;
1969 }
1970 return;
1971 }
1972 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001973 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 fPts += 3;
1975 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001976
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001978 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001979 }
1980 }
1981}
1982
reed@google.com4a3b7142012-05-16 17:16:46 +00001983SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001984 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 // Close the curve if requested and if there is some curve to close
1988 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001989 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001990 return kLine_Verb;
1991 }
1992 fNeedClose = false;
1993 return kClose_Verb;
1994 }
1995 return kDone_Verb;
1996 }
1997
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001998 // fVerbs is one beyond the current verb, decrement first
1999 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002000 const SkPoint* SK_RESTRICT srcPts = fPts;
2001 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002
2003 switch (verb) {
2004 case kMove_Verb:
2005 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002006 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002007 verb = this->autoClose(pts);
2008 if (verb == kClose_Verb) {
2009 fNeedClose = false;
2010 }
2011 return (Verb)verb;
2012 }
2013 if (fVerbs == fVerbStop) { // might be a trailing moveto
2014 return kDone_Verb;
2015 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002016 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002017 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002019 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002020 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021 fNeedClose = fForceClose;
2022 break;
2023 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002024 pts[0] = this->cons_moveTo();
2025 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026 fLastPt = srcPts[0];
2027 fCloseLine = false;
2028 srcPts += 1;
2029 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002030 case kConic_Verb:
2031 fConicWeights += 1;
2032 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002034 pts[0] = this->cons_moveTo();
2035 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036 fLastPt = srcPts[1];
2037 srcPts += 2;
2038 break;
2039 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002040 pts[0] = this->cons_moveTo();
2041 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 fLastPt = srcPts[2];
2043 srcPts += 3;
2044 break;
2045 case kClose_Verb:
2046 verb = this->autoClose(pts);
2047 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002048 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 } else {
2050 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002051 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002053 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 break;
2055 }
2056 fPts = srcPts;
2057 return (Verb)verb;
2058}
2059
2060///////////////////////////////////////////////////////////////////////////////
2061
Ben Wagner4d1955c2017-03-10 13:08:15 -05002062#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002063#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002064#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002065
reed@google.com51bbe752013-01-17 22:07:50 +00002066static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002067 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002068 str->append(label);
2069 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002070
reed@google.com51bbe752013-01-17 22:07:50 +00002071 const SkScalar* values = &pts[0].fX;
2072 count *= 2;
2073
2074 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002075 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002076 if (i < count - 1) {
2077 str->append(", ");
2078 }
2079 }
Mike Reed405b9d22018-01-09 15:11:08 -05002080 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002081 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002082 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002083 }
caryclark08fa28c2014-10-23 13:08:56 -07002084 str->append(");");
reede05fed02014-12-15 07:59:53 -08002085 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002086 str->append(" // ");
2087 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002088 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002089 if (i < count - 1) {
2090 str->append(", ");
2091 }
2092 }
2093 if (conicWeight >= 0) {
2094 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002095 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002096 }
2097 }
2098 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002099}
2100
caryclarke9562592014-09-15 09:26:09 -07002101void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002102 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103 Iter iter(*this, forceClose);
2104 SkPoint pts[4];
2105 Verb verb;
2106
reed@google.com51bbe752013-01-17 22:07:50 +00002107 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002108 char const * const gFillTypeStrs[] = {
2109 "Winding",
2110 "EvenOdd",
2111 "InverseWinding",
2112 "InverseEvenOdd",
2113 };
2114 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2115 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002116 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117 switch (verb) {
2118 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002119 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 break;
2121 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002122 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123 break;
2124 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002125 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002127 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002128 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002129 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002131 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 break;
2133 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002134 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
2136 default:
2137 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2138 verb = kDone_Verb; // stop the loop
2139 break;
2140 }
caryclark1049f122015-04-20 08:31:59 -07002141 if (!wStream && builder.size()) {
2142 SkDebugf("%s", builder.c_str());
2143 builder.reset();
2144 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 }
caryclark66a5d8b2014-06-24 08:30:15 -07002146 if (wStream) {
2147 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002148 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149}
2150
reed@android.come522ca52009-11-23 20:10:41 +00002151void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002152 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002153}
2154
2155void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002156 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002157}
2158
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002159
Cary Clark0461e9f2017-08-25 15:13:38 -04002160bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002161 if ((fFillType & ~3) != 0) {
2162 return false;
2163 }
reed@google.comabf15c12011-01-18 20:35:51 +00002164
djsollen@google.com077348c2012-10-22 20:23:32 +00002165#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002166 if (!fBoundsIsDirty) {
2167 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002168
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002169 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002170 if (SkToBool(fIsFinite) != isFinite) {
2171 return false;
2172 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002173
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002174 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002175 // if we're empty, fBounds may be empty but translated, so we can't
2176 // necessarily compare to bounds directly
2177 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2178 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002179 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2180 return false;
2181 }
reed@android.come522ca52009-11-23 20:10:41 +00002182 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002183 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002184 if (!fBounds.isEmpty()) {
2185 return false;
2186 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002187 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002188 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002189 if (!fBounds.contains(bounds)) {
2190 return false;
2191 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002192 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002193 }
reed@android.come522ca52009-11-23 20:10:41 +00002194 }
2195 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002196#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002197 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002198}
reed@android.come522ca52009-11-23 20:10:41 +00002199
reed@google.com04863fa2011-05-15 04:08:24 +00002200///////////////////////////////////////////////////////////////////////////////
2201
reed@google.com0b7b9822011-05-16 12:29:27 +00002202static int sign(SkScalar x) { return x < 0; }
2203#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002204
robertphillipsc506e302014-09-16 09:43:31 -07002205enum DirChange {
2206 kLeft_DirChange,
2207 kRight_DirChange,
2208 kStraight_DirChange,
2209 kBackwards_DirChange,
2210
2211 kInvalid_DirChange
2212};
2213
2214
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002215static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002216 // The error epsilon was empirically derived; worse case round rects
2217 // with a mid point outset by 2x float epsilon in tests had an error
2218 // of 12.
2219 const int epsilon = 16;
2220 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2221 return false;
2222 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002223 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002224 int aBits = SkFloatAs2sCompliment(compA);
2225 int bBits = SkFloatAs2sCompliment(compB);
2226 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002227}
2228
caryclarkb4216502015-03-02 13:02:34 -08002229static bool approximately_zero_when_compared_to(double x, double y) {
2230 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002231}
2232
caryclarkb4216502015-03-02 13:02:34 -08002233
reed@google.com04863fa2011-05-15 04:08:24 +00002234// only valid for a single contour
2235struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002236 Convexicator()
2237 : fPtCount(0)
2238 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002239 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002240 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002241 , fIsCurve(false)
2242 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002243 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002244 // warnings
djsollen2f124632016-04-29 13:53:05 -07002245 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002246 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002247 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002248 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002249 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002250
2251 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002252 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002253 }
2254
2255 SkPath::Convexity getConvexity() const { return fConvexity; }
2256
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002257 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002258 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002259
reed@google.com04863fa2011-05-15 04:08:24 +00002260 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002261 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002262 return;
2263 }
2264
2265 if (0 == fPtCount) {
2266 fCurrPt = pt;
2267 ++fPtCount;
2268 } else {
2269 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002270 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002271 if (!SkScalarIsFinite(lengthSqd)) {
2272 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002273 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002274 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002275 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002276 fCurrPt = pt;
2277 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002278 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002279 } else {
2280 SkASSERT(fPtCount > 2);
2281 this->addVec(vec);
2282 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002283
reed@google.com85b6e392011-05-15 20:25:17 +00002284 int sx = sign(vec.fX);
2285 int sy = sign(vec.fY);
2286 fDx += (sx != fSx);
2287 fDy += (sy != fSy);
2288 fSx = sx;
2289 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002290
reed@google.com85b6e392011-05-15 20:25:17 +00002291 if (fDx > 3 || fDy > 3) {
2292 fConvexity = SkPath::kConcave_Convexity;
2293 }
reed@google.com04863fa2011-05-15 04:08:24 +00002294 }
2295 }
2296 }
2297
2298 void close() {
2299 if (fPtCount > 2) {
2300 this->addVec(fFirstVec);
2301 }
2302 }
2303
caryclarkb4216502015-03-02 13:02:34 -08002304 DirChange directionChange(const SkVector& curVec) {
2305 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2306
2307 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2308 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2309 largest = SkTMax(largest, -smallest);
2310
2311 if (!almost_equal(largest, largest + cross)) {
2312 int sign = SkScalarSignAsInt(cross);
2313 if (sign) {
2314 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2315 }
2316 }
2317
2318 if (cross) {
2319 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2320 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2321 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2322 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2323 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2324 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2325 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2326 if (sign) {
2327 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2328 }
2329 }
2330 }
2331
Cary Clarkdf429f32017-11-08 11:44:31 -05002332 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2333 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2334 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2335 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002336 fLastVec.dot(curVec) < 0.0f) {
2337 return kBackwards_DirChange;
2338 }
2339
2340 return kStraight_DirChange;
2341 }
2342
Cary Clarkc8323aa2017-08-25 08:04:43 -04002343 bool hasBackwards() const {
2344 return fBackwards;
2345 }
caryclarkb4216502015-03-02 13:02:34 -08002346
caryclarkd3d1a982014-12-08 04:57:38 -08002347 bool isFinite() const {
2348 return fIsFinite;
2349 }
2350
caryclark5ccef572015-03-02 10:07:56 -08002351 void setCurve(bool isCurve) {
2352 fIsCurve = isCurve;
2353 }
2354
reed@google.com04863fa2011-05-15 04:08:24 +00002355private:
2356 void addVec(const SkVector& vec) {
2357 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002358 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002359 switch (dir) {
2360 case kLeft_DirChange: // fall through
2361 case kRight_DirChange:
2362 if (kInvalid_DirChange == fExpectedDir) {
2363 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002364 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2365 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002366 } else if (dir != fExpectedDir) {
2367 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002368 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002369 }
2370 fLastVec = vec;
2371 break;
2372 case kStraight_DirChange:
2373 break;
2374 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002375 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002376 // If any of the subsequent dir is non-backward, it'll be concave.
2377 // Otherwise, it's still convex.
2378 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002379 }
robertphillipsc506e302014-09-16 09:43:31 -07002380 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002381 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002382 break;
2383 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002384 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002385 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002386 }
2387 }
2388
caryclarkb4216502015-03-02 13:02:34 -08002389 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002390 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002391 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002392 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2393 // value with the current vec is deemed to be of a significant value.
2394 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002395 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002396 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002397 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002398 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002399 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002400 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002401 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002402 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002403};
2404
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002405SkPath::Convexity SkPath::internalGetConvexity() const {
2406 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002407 SkPoint pts[4];
2408 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002409 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002410
2411 int contourCount = 0;
2412 int count;
2413 Convexicator state;
2414
caryclarkd3d1a982014-12-08 04:57:38 -08002415 if (!isFinite()) {
2416 return kUnknown_Convexity;
2417 }
Brian Osman205a1262017-09-18 13:13:48 +00002418 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002419 switch (verb) {
2420 case kMove_Verb:
2421 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002423 return kConcave_Convexity;
2424 }
2425 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002426 // fall through
2427 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002428 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002429 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002430 break;
caryclark5ccef572015-03-02 10:07:56 -08002431 case kQuad_Verb:
2432 // fall through
2433 case kConic_Verb:
2434 // fall through
2435 case kCubic_Verb:
2436 count = 2 + (kCubic_Verb == verb);
2437 // As an additional enhancement, this could set curve true only
2438 // if the curve is nonlinear
2439 state.setCurve(true);
2440 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002442 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002443 state.close();
2444 count = 0;
2445 break;
2446 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002447 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002449 return kConcave_Convexity;
2450 }
2451
2452 for (int i = 1; i <= count; i++) {
2453 state.addPt(pts[i]);
2454 }
2455 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002456 if (!state.isFinite()) {
2457 return kUnknown_Convexity;
2458 }
reed@google.com04863fa2011-05-15 04:08:24 +00002459 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002460 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002461 return kConcave_Convexity;
2462 }
2463 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002464 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002465 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002466 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2467 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2468 fConvexity = Convexity::kConcave_Convexity;
2469 } else {
2470 fFirstDirection = state.getFirstDirection();
2471 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002472 }
2473 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002474}
reed@google.com69a99432012-01-10 18:00:10 +00002475
2476///////////////////////////////////////////////////////////////////////////////
2477
2478class ContourIter {
2479public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002480 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002481
2482 bool done() const { return fDone; }
2483 // if !done() then these may be called
2484 int count() const { return fCurrPtCount; }
2485 const SkPoint* pts() const { return fCurrPt; }
2486 void next();
2487
2488private:
2489 int fCurrPtCount;
2490 const SkPoint* fCurrPt;
2491 const uint8_t* fCurrVerb;
2492 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002493 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002494 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002495 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002496};
2497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002498ContourIter::ContourIter(const SkPathRef& pathRef) {
2499 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002500 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002501 fCurrPt = pathRef.points();
2502 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002503 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002504 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002505 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002506 this->next();
2507}
2508
2509void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002510 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002511 fDone = true;
2512 }
2513 if (fDone) {
2514 return;
2515 }
2516
2517 // skip pts of prev contour
2518 fCurrPt += fCurrPtCount;
2519
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002520 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002521 int ptCount = 1; // moveTo
2522 const uint8_t* verbs = fCurrVerb;
2523
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002524 for (--verbs; verbs > fStopVerbs; --verbs) {
2525 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002526 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002527 goto CONTOUR_END;
2528 case SkPath::kLine_Verb:
2529 ptCount += 1;
2530 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002531 case SkPath::kConic_Verb:
2532 fCurrConicWeight += 1;
2533 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002534 case SkPath::kQuad_Verb:
2535 ptCount += 2;
2536 break;
2537 case SkPath::kCubic_Verb:
2538 ptCount += 3;
2539 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002540 case SkPath::kClose_Verb:
2541 break;
2542 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002543 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002544 break;
2545 }
2546 }
2547CONTOUR_END:
2548 fCurrPtCount = ptCount;
2549 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002550 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002551}
2552
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002553// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002554static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002555 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2556 // We may get 0 when the above subtracts underflow. We expect this to be
2557 // very rare and lazily promote to double.
2558 if (0 == cross) {
2559 double p0x = SkScalarToDouble(p0.fX);
2560 double p0y = SkScalarToDouble(p0.fY);
2561
2562 double p1x = SkScalarToDouble(p1.fX);
2563 double p1y = SkScalarToDouble(p1.fY);
2564
2565 double p2x = SkScalarToDouble(p2.fX);
2566 double p2y = SkScalarToDouble(p2.fY);
2567
2568 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2569 (p1y - p0y) * (p2x - p0x));
2570
2571 }
2572 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002573}
2574
reed@google.comc1ea60a2012-01-31 15:15:36 +00002575// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002576static int find_max_y(const SkPoint pts[], int count) {
2577 SkASSERT(count > 0);
2578 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002579 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002580 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002581 SkScalar y = pts[i].fY;
2582 if (y > max) {
2583 max = y;
2584 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002585 }
2586 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002587 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002588}
2589
reed@google.comcabaf1d2012-01-11 21:03:05 +00002590static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2591 int i = index;
2592 for (;;) {
2593 i = (i + inc) % n;
2594 if (i == index) { // we wrapped around, so abort
2595 break;
2596 }
2597 if (pts[index] != pts[i]) { // found a different point, success!
2598 break;
2599 }
2600 }
2601 return i;
2602}
2603
reed@google.comc1ea60a2012-01-31 15:15:36 +00002604/**
2605 * Starting at index, and moving forward (incrementing), find the xmin and
2606 * xmax of the contiguous points that have the same Y.
2607 */
2608static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2609 int* maxIndexPtr) {
2610 const SkScalar y = pts[index].fY;
2611 SkScalar min = pts[index].fX;
2612 SkScalar max = min;
2613 int minIndex = index;
2614 int maxIndex = index;
2615 for (int i = index + 1; i < n; ++i) {
2616 if (pts[i].fY != y) {
2617 break;
2618 }
2619 SkScalar x = pts[i].fX;
2620 if (x < min) {
2621 min = x;
2622 minIndex = i;
2623 } else if (x > max) {
2624 max = x;
2625 maxIndex = i;
2626 }
2627 }
2628 *maxIndexPtr = maxIndex;
2629 return minIndex;
2630}
2631
reed026beb52015-06-10 14:23:15 -07002632static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2633 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002634}
2635
reed@google.comac8543f2012-01-30 20:51:25 +00002636/*
2637 * We loop through all contours, and keep the computed cross-product of the
2638 * contour that contained the global y-max. If we just look at the first
2639 * contour, we may find one that is wound the opposite way (correctly) since
2640 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2641 * that is outer most (or at least has the global y-max) before we can consider
2642 * its cross product.
2643 */
reed026beb52015-06-10 14:23:15 -07002644bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002645 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2646 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002647 return true;
2648 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002649
2650 // don't want to pay the cost for computing this if it
2651 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002652 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2653 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002654 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002655 return false;
2656 }
reed@google.com69a99432012-01-10 18:00:10 +00002657
reed026beb52015-06-10 14:23:15 -07002658 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002659
reed@google.comac8543f2012-01-30 20:51:25 +00002660 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002661 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002662 SkScalar ymaxCross = 0;
2663
reed@google.com69a99432012-01-10 18:00:10 +00002664 for (; !iter.done(); iter.next()) {
2665 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002666 if (n < 3) {
2667 continue;
2668 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002669
reed@google.comcabaf1d2012-01-11 21:03:05 +00002670 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002671 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002672 int index = find_max_y(pts, n);
2673 if (pts[index].fY < ymax) {
2674 continue;
2675 }
2676
2677 // If there is more than 1 distinct point at the y-max, we take the
2678 // x-min and x-max of them and just subtract to compute the dir.
2679 if (pts[(index + 1) % n].fY == pts[index].fY) {
2680 int maxIndex;
2681 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2682 if (minIndex == maxIndex) {
2683 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002684 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002685 SkASSERT(pts[minIndex].fY == pts[index].fY);
2686 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2687 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2688 // we just subtract the indices, and let that auto-convert to
2689 // SkScalar, since we just want - or + to signal the direction.
2690 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002691 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002692 TRY_CROSSPROD:
2693 // Find a next and prev index to use for the cross-product test,
2694 // but we try to find pts that form non-zero vectors from pts[index]
2695 //
2696 // Its possible that we can't find two non-degenerate vectors, so
2697 // we have to guard our search (e.g. all the pts could be in the
2698 // same place).
2699
2700 // we pass n - 1 instead of -1 so we don't foul up % operator by
2701 // passing it a negative LH argument.
2702 int prev = find_diff_pt(pts, index, n, n - 1);
2703 if (prev == index) {
2704 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002705 continue;
2706 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002707 int next = find_diff_pt(pts, index, n, 1);
2708 SkASSERT(next != index);
2709 cross = cross_prod(pts[prev], pts[index], pts[next]);
2710 // if we get a zero and the points are horizontal, then we look at the spread in
2711 // x-direction. We really should continue to walk away from the degeneracy until
2712 // there is a divergence.
2713 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2714 // construct the subtract so we get the correct Direction below
2715 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002716 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002717 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002718
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002719 if (cross) {
2720 // record our best guess so far
2721 ymax = pts[index].fY;
2722 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002723 }
2724 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002725 if (ymaxCross) {
2726 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002727 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002728 return true;
2729 } else {
2730 return false;
2731 }
reed@google.comac8543f2012-01-30 20:51:25 +00002732}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002733
2734///////////////////////////////////////////////////////////////////////////////
2735
caryclark9aacd902015-12-14 08:38:09 -08002736static bool between(SkScalar a, SkScalar b, SkScalar c) {
2737 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2738 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2739 return (a - b) * (c - b) <= 0;
2740}
2741
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002742static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2743 SkScalar t) {
2744 SkScalar A = c3 + 3*(c1 - c2) - c0;
2745 SkScalar B = 3*(c2 - c1 - c1 + c0);
2746 SkScalar C = 3*(c1 - c0);
2747 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002748 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749}
2750
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751template <size_t N> static void find_minmax(const SkPoint pts[],
2752 SkScalar* minPtr, SkScalar* maxPtr) {
2753 SkScalar min, max;
2754 min = max = pts[0].fX;
2755 for (size_t i = 1; i < N; ++i) {
2756 min = SkMinScalar(min, pts[i].fX);
2757 max = SkMaxScalar(max, pts[i].fX);
2758 }
2759 *minPtr = min;
2760 *maxPtr = max;
2761}
2762
caryclark9cb5d752015-12-18 04:35:24 -08002763static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2764 if (start.fY == end.fY) {
2765 return between(start.fX, x, end.fX) && x != end.fX;
2766 } else {
2767 return x == start.fX && y == start.fY;
2768 }
2769}
2770
caryclark9aacd902015-12-14 08:38:09 -08002771static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002772 SkScalar y0 = pts[0].fY;
2773 SkScalar y3 = pts[3].fY;
2774
2775 int dir = 1;
2776 if (y0 > y3) {
2777 SkTSwap(y0, y3);
2778 dir = -1;
2779 }
2780 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781 return 0;
2782 }
caryclark9cb5d752015-12-18 04:35:24 -08002783 if (checkOnCurve(x, y, pts[0], pts[3])) {
2784 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002785 return 0;
2786 }
caryclark9cb5d752015-12-18 04:35:24 -08002787 if (y == y3) {
2788 return 0;
2789 }
2790
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002791 // quickreject or quickaccept
2792 SkScalar min, max;
2793 find_minmax<4>(pts, &min, &max);
2794 if (x < min) {
2795 return 0;
2796 }
2797 if (x > max) {
2798 return dir;
2799 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002800
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002801 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002802 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002803 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002804 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002805 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002806 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002807 if (SkScalarNearlyEqual(xt, x)) {
2808 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2809 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002810 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002811 }
2812 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 return xt < x ? dir : 0;
2814}
2815
caryclark9aacd902015-12-14 08:38:09 -08002816static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 SkPoint dst[10];
2818 int n = SkChopCubicAtYExtrema(pts, dst);
2819 int w = 0;
2820 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002821 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 }
2823 return w;
2824}
2825
caryclark9aacd902015-12-14 08:38:09 -08002826static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2827 SkASSERT(src);
2828 SkASSERT(t >= 0 && t <= 1);
2829 SkScalar src2w = src[2] * w;
2830 SkScalar C = src[0];
2831 SkScalar A = src[4] - 2 * src2w + C;
2832 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002833 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002834}
2835
2836
2837static double conic_eval_denominator(SkScalar w, SkScalar t) {
2838 SkScalar B = 2 * (w - 1);
2839 SkScalar C = 1;
2840 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002841 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002842}
2843
2844static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2845 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846 SkScalar y0 = pts[0].fY;
2847 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002848
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 int dir = 1;
2850 if (y0 > y2) {
2851 SkTSwap(y0, y2);
2852 dir = -1;
2853 }
caryclark9aacd902015-12-14 08:38:09 -08002854 if (y < y0 || y > y2) {
2855 return 0;
2856 }
caryclark9cb5d752015-12-18 04:35:24 -08002857 if (checkOnCurve(x, y, pts[0], pts[2])) {
2858 *onCurveCount += 1;
2859 return 0;
2860 }
caryclark9aacd902015-12-14 08:38:09 -08002861 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 return 0;
2863 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002864
caryclark9aacd902015-12-14 08:38:09 -08002865 SkScalar roots[2];
2866 SkScalar A = pts[2].fY;
2867 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2868 SkScalar C = pts[0].fY;
2869 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2870 B -= C; // B = b*w - w * yCept + yCept - a
2871 C -= y;
2872 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2873 SkASSERT(n <= 1);
2874 SkScalar xt;
2875 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002876 // zero roots are returned only when y0 == y
2877 // Need [0] if dir == 1
2878 // and [2] if dir == -1
2879 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002880 } else {
2881 SkScalar t = roots[0];
2882 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2883 }
2884 if (SkScalarNearlyEqual(xt, x)) {
2885 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2886 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002887 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002888 }
2889 }
2890 return xt < x ? dir : 0;
2891}
2892
2893static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2894 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2895 if (y0 == y1) {
2896 return true;
2897 }
2898 if (y0 < y1) {
2899 return y1 <= y2;
2900 } else {
2901 return y1 >= y2;
2902 }
2903}
2904
2905static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2906 int* onCurveCount) {
2907 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002908 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002909 // If the data points are very large, the conic may not be monotonic but may also
2910 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002911 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2912 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2913 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002914 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2915 }
2916 return w;
2917}
2918
2919static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2920 SkScalar y0 = pts[0].fY;
2921 SkScalar y2 = pts[2].fY;
2922
2923 int dir = 1;
2924 if (y0 > y2) {
2925 SkTSwap(y0, y2);
2926 dir = -1;
2927 }
2928 if (y < y0 || y > y2) {
2929 return 0;
2930 }
caryclark9cb5d752015-12-18 04:35:24 -08002931 if (checkOnCurve(x, y, pts[0], pts[2])) {
2932 *onCurveCount += 1;
2933 return 0;
2934 }
caryclark9aacd902015-12-14 08:38:09 -08002935 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002936 return 0;
2937 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002938 // bounds check on X (not required. is it faster?)
2939#if 0
2940 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2941 return 0;
2942 }
2943#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002944
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002945 SkScalar roots[2];
2946 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2947 2 * (pts[1].fY - pts[0].fY),
2948 pts[0].fY - y,
2949 roots);
2950 SkASSERT(n <= 1);
2951 SkScalar xt;
2952 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002953 // zero roots are returned only when y0 == y
2954 // Need [0] if dir == 1
2955 // and [2] if dir == -1
2956 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002957 } else {
2958 SkScalar t = roots[0];
2959 SkScalar C = pts[0].fX;
2960 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2961 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002962 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002963 }
caryclark9aacd902015-12-14 08:38:09 -08002964 if (SkScalarNearlyEqual(xt, x)) {
2965 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2966 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002967 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002968 }
2969 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002970 return xt < x ? dir : 0;
2971}
2972
caryclark9aacd902015-12-14 08:38:09 -08002973static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002974 SkPoint dst[5];
2975 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002976
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2978 n = SkChopQuadAtYExtrema(pts, dst);
2979 pts = dst;
2980 }
caryclark9aacd902015-12-14 08:38:09 -08002981 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002982 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002983 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002984 }
2985 return w;
2986}
2987
caryclark9aacd902015-12-14 08:38:09 -08002988static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002989 SkScalar x0 = pts[0].fX;
2990 SkScalar y0 = pts[0].fY;
2991 SkScalar x1 = pts[1].fX;
2992 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002993
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002994 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002995
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 int dir = 1;
2997 if (y0 > y1) {
2998 SkTSwap(y0, y1);
2999 dir = -1;
3000 }
caryclark9aacd902015-12-14 08:38:09 -08003001 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 return 0;
3003 }
caryclark9cb5d752015-12-18 04:35:24 -08003004 if (checkOnCurve(x, y, pts[0], pts[1])) {
3005 *onCurveCount += 1;
3006 return 0;
3007 }
3008 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003009 return 0;
3010 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003011 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003012
caryclark9aacd902015-12-14 08:38:09 -08003013 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003014 // zero cross means the point is on the line, and since the case where
3015 // y of the query point is at the end point is handled above, we can be
3016 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003017 if (x != x1 || y != pts[1].fY) {
3018 *onCurveCount += 1;
3019 }
caryclark9aacd902015-12-14 08:38:09 -08003020 dir = 0;
3021 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003022 dir = 0;
3023 }
3024 return dir;
3025}
3026
caryclark9aacd902015-12-14 08:38:09 -08003027static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3028 SkTDArray<SkVector>* tangents) {
3029 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3030 && !between(pts[2].fY, y, pts[3].fY)) {
3031 return;
3032 }
3033 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3034 && !between(pts[2].fX, x, pts[3].fX)) {
3035 return;
3036 }
3037 SkPoint dst[10];
3038 int n = SkChopCubicAtYExtrema(pts, dst);
3039 for (int i = 0; i <= n; ++i) {
3040 SkPoint* c = &dst[i * 3];
3041 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003042 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003043 continue;
mbarbella276e6332016-05-31 14:44:01 -07003044 }
caryclark9aacd902015-12-14 08:38:09 -08003045 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3046 if (!SkScalarNearlyEqual(x, xt)) {
3047 continue;
3048 }
3049 SkVector tangent;
3050 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3051 tangents->push(tangent);
3052 }
3053}
3054
3055static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3056 SkTDArray<SkVector>* tangents) {
3057 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3058 return;
3059 }
3060 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3061 return;
3062 }
3063 SkScalar roots[2];
3064 SkScalar A = pts[2].fY;
3065 SkScalar B = pts[1].fY * w - y * w + y;
3066 SkScalar C = pts[0].fY;
3067 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3068 B -= C; // B = b*w - w * yCept + yCept - a
3069 C -= y;
3070 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3071 for (int index = 0; index < n; ++index) {
3072 SkScalar t = roots[index];
3073 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3074 if (!SkScalarNearlyEqual(x, xt)) {
3075 continue;
3076 }
3077 SkConic conic(pts, w);
3078 tangents->push(conic.evalTangentAt(t));
3079 }
3080}
3081
3082static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3083 SkTDArray<SkVector>* tangents) {
3084 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3085 return;
3086 }
3087 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3088 return;
3089 }
3090 SkScalar roots[2];
3091 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3092 2 * (pts[1].fY - pts[0].fY),
3093 pts[0].fY - y,
3094 roots);
3095 for (int index = 0; index < n; ++index) {
3096 SkScalar t = roots[index];
3097 SkScalar C = pts[0].fX;
3098 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3099 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003100 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003101 if (!SkScalarNearlyEqual(x, xt)) {
3102 continue;
3103 }
3104 tangents->push(SkEvalQuadTangentAt(pts, t));
3105 }
3106}
3107
3108static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3109 SkTDArray<SkVector>* tangents) {
3110 SkScalar y0 = pts[0].fY;
3111 SkScalar y1 = pts[1].fY;
3112 if (!between(y0, y, y1)) {
3113 return;
3114 }
3115 SkScalar x0 = pts[0].fX;
3116 SkScalar x1 = pts[1].fX;
3117 if (!between(x0, x, x1)) {
3118 return;
3119 }
3120 SkScalar dx = x1 - x0;
3121 SkScalar dy = y1 - y0;
3122 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3123 return;
3124 }
3125 SkVector v;
3126 v.set(dx, dy);
3127 tangents->push(v);
3128}
3129
reed@google.com4db592c2013-10-30 17:39:43 +00003130static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3131 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3132}
3133
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003134bool SkPath::contains(SkScalar x, SkScalar y) const {
3135 bool isInverse = this->isInverseFillType();
3136 if (this->isEmpty()) {
3137 return isInverse;
3138 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003139
reed@google.com4db592c2013-10-30 17:39:43 +00003140 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003141 return isInverse;
3142 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003143
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003144 SkPath::Iter iter(*this, true);
3145 bool done = false;
3146 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003147 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003148 do {
3149 SkPoint pts[4];
3150 switch (iter.next(pts, false)) {
3151 case SkPath::kMove_Verb:
3152 case SkPath::kClose_Verb:
3153 break;
3154 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003155 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003156 break;
3157 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003158 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003159 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003160 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003161 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003162 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003163 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003164 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003165 break;
3166 case SkPath::kDone_Verb:
3167 done = true;
3168 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003169 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003170 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003171 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3172 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3173 if (evenOddFill) {
3174 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003175 }
caryclark9aacd902015-12-14 08:38:09 -08003176 if (w) {
3177 return !isInverse;
3178 }
3179 if (onCurveCount <= 1) {
3180 return SkToBool(onCurveCount) ^ isInverse;
3181 }
3182 if ((onCurveCount & 1) || evenOddFill) {
3183 return SkToBool(onCurveCount & 1) ^ isInverse;
3184 }
halcanary9d524f22016-03-29 09:03:52 -07003185 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003186 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3187 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003188 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003189 SkTDArray<SkVector> tangents;
3190 do {
3191 SkPoint pts[4];
3192 int oldCount = tangents.count();
3193 switch (iter.next(pts, false)) {
3194 case SkPath::kMove_Verb:
3195 case SkPath::kClose_Verb:
3196 break;
3197 case SkPath::kLine_Verb:
3198 tangent_line(pts, x, y, &tangents);
3199 break;
3200 case SkPath::kQuad_Verb:
3201 tangent_quad(pts, x, y, &tangents);
3202 break;
3203 case SkPath::kConic_Verb:
3204 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3205 break;
3206 case SkPath::kCubic_Verb:
3207 tangent_cubic(pts, x, y, &tangents);
3208 break;
3209 case SkPath::kDone_Verb:
3210 done = true;
3211 break;
3212 }
3213 if (tangents.count() > oldCount) {
3214 int last = tangents.count() - 1;
3215 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003216 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003217 tangents.remove(last);
3218 } else {
3219 for (int index = 0; index < last; ++index) {
3220 const SkVector& test = tangents[index];
3221 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003222 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3223 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003224 tangents.remove(last);
3225 tangents.removeShuffle(index);
3226 break;
3227 }
3228 }
3229 }
3230 }
3231 } while (!done);
3232 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003233}
fmalitaaa0df4e2015-12-01 09:13:23 -08003234
3235int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3236 SkScalar w, SkPoint pts[], int pow2) {
3237 const SkConic conic(p0, p1, p2, w);
3238 return conic.chopIntoQuadsPOW2(pts, pow2);
3239}
bsalomonedc743a2016-06-01 09:42:31 -07003240
3241bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3242 unsigned* start) {
3243 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3244 return false;
3245 }
3246 SkPath::RawIter iter(path);
3247 SkPoint verbPts[4];
3248 SkPath::Verb v;
3249 SkPoint rectPts[5];
3250 int rectPtCnt = 0;
3251 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3252 switch (v) {
3253 case SkPath::kMove_Verb:
3254 if (0 != rectPtCnt) {
3255 return false;
3256 }
3257 rectPts[0] = verbPts[0];
3258 ++rectPtCnt;
3259 break;
3260 case SkPath::kLine_Verb:
3261 if (5 == rectPtCnt) {
3262 return false;
3263 }
3264 rectPts[rectPtCnt] = verbPts[1];
3265 ++rectPtCnt;
3266 break;
3267 case SkPath::kClose_Verb:
3268 if (4 == rectPtCnt) {
3269 rectPts[4] = rectPts[0];
3270 rectPtCnt = 5;
3271 }
3272 break;
3273 default:
3274 return false;
3275 }
3276 }
3277 if (rectPtCnt < 5) {
3278 return false;
3279 }
3280 if (rectPts[0] != rectPts[4]) {
3281 return false;
3282 }
bsalomon057ae8a2016-07-24 05:37:26 -07003283 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3284 // and pts 1 and 2 the opposite vertical or horizontal edge).
3285 bool vec03IsVertical;
3286 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3287 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3288 // Make sure it has non-zero width and height
3289 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003290 return false;
3291 }
bsalomon057ae8a2016-07-24 05:37:26 -07003292 vec03IsVertical = true;
3293 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3294 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3295 // Make sure it has non-zero width and height
3296 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3297 return false;
3298 }
3299 vec03IsVertical = false;
3300 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003301 return false;
3302 }
bsalomon057ae8a2016-07-24 05:37:26 -07003303 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3304 // set if it is on the bottom edge.
3305 unsigned sortFlags =
3306 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3307 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3308 switch (sortFlags) {
3309 case 0b00:
3310 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3311 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3312 *start = 0;
3313 break;
3314 case 0b01:
3315 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3316 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3317 *start = 1;
3318 break;
3319 case 0b10:
3320 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3321 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3322 *start = 3;
3323 break;
3324 case 0b11:
3325 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3326 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3327 *start = 2;
3328 break;
bsalomonedc743a2016-06-01 09:42:31 -07003329 }
bsalomonedc743a2016-06-01 09:42:31 -07003330 return true;
3331}
bsalomon21af9ca2016-08-25 12:29:23 -07003332
3333void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3334 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3335 SkASSERT(!oval.isEmpty());
3336 SkASSERT(sweepAngle);
3337
3338 path->reset();
3339 path->setIsVolatile(true);
3340 path->setFillType(SkPath::kWinding_FillType);
3341 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3342 path->addOval(oval);
3343 return;
3344 }
3345 if (useCenter) {
3346 path->moveTo(oval.centerX(), oval.centerY());
3347 }
3348 // Arc to mods at 360 and drawArc is not supposed to.
3349 bool forceMoveTo = !useCenter;
3350 while (sweepAngle <= -360.f) {
3351 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3352 startAngle -= 180.f;
3353 path->arcTo(oval, startAngle, -180.f, false);
3354 startAngle -= 180.f;
3355 forceMoveTo = false;
3356 sweepAngle += 360.f;
3357 }
3358 while (sweepAngle >= 360.f) {
3359 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3360 startAngle += 180.f;
3361 path->arcTo(oval, startAngle, 180.f, false);
3362 startAngle += 180.f;
3363 forceMoveTo = false;
3364 sweepAngle -= 360.f;
3365 }
3366 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3367 if (useCenter) {
3368 path->close();
3369 }
3370}
Mike Reed0d7dac82017-02-02 17:45:56 -08003371
3372///////////////////////////////////////////////////////////////////////////////////////////////////
3373#include "SkNx.h"
3374
3375static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3376 SkScalar ts[2];
3377 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3378 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3379 SkASSERT(n >= 0 && n <= 2);
3380 for (int i = 0; i < n; ++i) {
3381 extremas[i] = SkEvalQuadAt(src, ts[i]);
3382 }
3383 extremas[n] = src[2];
3384 return n + 1;
3385}
3386
3387static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3388 SkConic conic(src[0], src[1], src[2], w);
3389 SkScalar ts[2];
3390 int n = conic.findXExtrema(ts);
3391 n += conic.findYExtrema(&ts[n]);
3392 SkASSERT(n >= 0 && n <= 2);
3393 for (int i = 0; i < n; ++i) {
3394 extremas[i] = conic.evalAt(ts[i]);
3395 }
3396 extremas[n] = src[2];
3397 return n + 1;
3398}
3399
3400static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3401 SkScalar ts[4];
3402 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3403 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3404 SkASSERT(n >= 0 && n <= 4);
3405 for (int i = 0; i < n; ++i) {
3406 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3407 }
3408 extremas[n] = src[3];
3409 return n + 1;
3410}
3411
Mike Reed8d3196b2017-02-03 11:34:13 -05003412SkRect SkPath::computeTightBounds() const {
3413 if (0 == this->countVerbs()) {
3414 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003415 }
3416
Mike Reed8d3196b2017-02-03 11:34:13 -05003417 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3418 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003419 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003420
Mike Reed0d7dac82017-02-02 17:45:56 -08003421 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3422 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003423 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003424
3425 // initial with the first MoveTo, so we don't have to check inside the switch
3426 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003427 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003428 for (;;) {
3429 int count = 0;
3430 switch (iter.next(pts)) {
3431 case SkPath::kMove_Verb:
3432 extremas[0] = pts[0];
3433 count = 1;
3434 break;
3435 case SkPath::kLine_Verb:
3436 extremas[0] = pts[1];
3437 count = 1;
3438 break;
3439 case SkPath::kQuad_Verb:
3440 count = compute_quad_extremas(pts, extremas);
3441 break;
3442 case SkPath::kConic_Verb:
3443 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3444 break;
3445 case SkPath::kCubic_Verb:
3446 count = compute_cubic_extremas(pts, extremas);
3447 break;
3448 case SkPath::kClose_Verb:
3449 break;
3450 case SkPath::kDone_Verb:
3451 goto DONE;
3452 }
3453 for (int i = 0; i < count; ++i) {
3454 Sk2s tmp = from_point(extremas[i]);
3455 min = Sk2s::Min(min, tmp);
3456 max = Sk2s::Max(max, tmp);
3457 }
3458 }
3459DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003460 SkRect bounds;
3461 min.store((SkPoint*)&bounds.fLeft);
3462 max.store((SkPoint*)&bounds.fRight);
3463 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003464}
Cary Clarkdf429f32017-11-08 11:44:31 -05003465
3466bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3467 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3468}
3469
3470bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3471 const SkPoint& p3, bool exact) {
3472 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3473 SkPointPriv::EqualsWithinTolerance(p2, p3);
3474}
3475
3476bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3477 const SkPoint& p3, const SkPoint& p4, bool exact) {
3478 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3479 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3480 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3481 SkPointPriv::EqualsWithinTolerance(p3, p4);
3482}