blob: 9e242376c0a5c211c2da716f7c1826da466b93b4 [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;
Cary Clark8540e112018-04-11 14:30:27 -0400450 SkPoint previous; // used to construct line from previous point
451 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
452 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
caryclark@google.com56f233a2012-11-19 13:06:06 +0000453 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400454 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
455 previous.set(0, 0);
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000456 int firstDirection = 0;
457 int lastDirection = 0;
458 int nextDirection = 0;
459 bool closedOrMoved = false;
Cary Clark8540e112018-04-11 14:30:27 -0400460 bool addedLine = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700462 bool insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400463 bool accumulatingRect = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000464 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000465 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700466 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
467 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000468 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000469 savePts = pts;
470 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700472 insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400473 accumulatingRect = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 case kLine_Verb: {
Cary Clark8540e112018-04-11 14:30:27 -0400475 SkScalar left = previous.fX;
476 SkScalar top = previous.fY;
caryclark@google.comf1316942011-07-26 19:54:45 +0000477 SkScalar right = pts->fX;
478 SkScalar bottom = pts->fY;
Cary Clark8540e112018-04-11 14:30:27 -0400479 if (accumulatingRect) {
480 lastPt = pts;
481 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 ++pts;
483 if (left != right && top != bottom) {
484 return false; // diagonal
485 }
Cary Clark8540e112018-04-11 14:30:27 -0400486 addedLine = true;
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 if (left == right && top == bottom) {
488 break; // single point on side OK
489 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000490 nextDirection = rect_make_dir(right - left, bottom - top);
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 if (0 == corners) {
492 firstDirection = nextDirection;
Cary Clark8540e112018-04-11 14:30:27 -0400493 previous = pts[-1];
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 corners = 1;
495 closedOrMoved = false;
496 break;
497 }
498 if (closedOrMoved) {
499 return false; // closed followed by a line
500 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000501 if (autoClose && nextDirection == firstDirection) {
502 break; // colinear with first
503 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000504 closedOrMoved = autoClose;
505 if (lastDirection != nextDirection) {
506 if (++corners > 4) {
507 return false; // too many direction changes
508 }
509 }
Cary Clark8540e112018-04-11 14:30:27 -0400510 previous = pts[-1];
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 if (lastDirection == nextDirection) {
512 break; // colinear segment
513 }
514 // Possible values for corners are 2, 3, and 4.
515 // When corners == 3, nextDirection opposes firstDirection.
516 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000517 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000518 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
519 if ((directionCycle ^ turn) != nextDirection) {
520 return false; // direction didn't follow cycle
521 }
522 break;
523 }
524 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000525 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000526 case kCubic_Verb:
527 return false; // quadratic, cubic not allowed
528 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700529 if (allowPartial && !autoClose && firstDirection) {
530 insertClose = true;
531 *currVerb -= 1; // try move again afterwards
532 goto addMissingClose;
533 }
Cary Clark8540e112018-04-11 14:30:27 -0400534 if (!addedLine) {
535 firstPt = pts;
536 accumulatingRect = true;
537 } else {
538 accumulatingRect = false;
539 }
540 previous = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000541 closedOrMoved = true;
542 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000543 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000544 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000545 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000546 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000547 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000548 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700549addMissingClose:
550 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000551 }
552 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400553 if (corners < 3 || corners > 4) {
554 return false;
555 }
556 SkPoint closeXY = *firstPt - *lastPt;
Cary Clark58627cb2018-04-10 09:16:41 -0400557 // If autoClose, check if close generates diagonal
Cary Clark8540e112018-04-11 14:30:27 -0400558 bool result = 4 == corners && (closeXY.isZero() || (autoClose && (!closeXY.fX || !closeXY.fY)));
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000559 if (!result) {
560 // check if we are just an incomplete rectangle, in which case we can
561 // return true, but not claim to be closed.
562 // e.g.
563 // 3 sided rectangle
564 // 4 sided but the last edge is not long enough to reach the start
565 //
Cary Clark8540e112018-04-11 14:30:27 -0400566 if (closeXY.fX && closeXY.fY) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000567 return false; // we're diagonal, abort (can we ever reach this?)
568 }
Cary Clark8540e112018-04-11 14:30:27 -0400569 int closeDirection = rect_make_dir(closeXY.fX, closeXY.fY);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000570 // make sure the close-segment doesn't double-back on itself
Cary Clark8540e112018-04-11 14:30:27 -0400571 if (3 == corners || closeDirection == lastDirection) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000572 result = true;
573 autoClose = false; // we are not closed
574 }
575 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000576 if (savePts) {
577 *ptsPtr = savePts;
578 }
Cary Clark5c715182018-04-09 16:07:11 -0400579 if (result && rect) {
Cary Clark8540e112018-04-11 14:30:27 -0400580 ptrdiff_t count = lastPt - firstPt + 1;
Cary Clark5c715182018-04-09 16:07:11 -0400581 rect->set(firstPt, (int) count);
582 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000583 if (result && isClosed) {
584 *isClosed = autoClose;
585 }
586 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000587 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000588 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000589 return result;
590}
591
robertphillips4f662e62014-12-29 14:06:51 -0800592bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000593 SkDEBUGCODE(this->validate();)
594 int currVerb = 0;
595 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400596 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000597}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000598
caryclark95bc5f32015-04-08 08:34:15 -0700599bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000600 SkDEBUGCODE(this->validate();)
601 int currVerb = 0;
602 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000603 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400604 SkRect testRects[2];
605 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000606 return false;
607 }
Cary Clark5c715182018-04-09 16:07:11 -0400608 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 if (testRects[0].contains(testRects[1])) {
610 if (rects) {
611 rects[0] = testRects[0];
612 rects[1] = testRects[1];
613 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000614 if (dirs) {
615 dirs[0] = testDirs[0];
616 dirs[1] = testDirs[1];
617 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000618 return true;
619 }
620 if (testRects[1].contains(testRects[0])) {
621 if (rects) {
622 rects[0] = testRects[1];
623 rects[1] = testRects[0];
624 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000625 if (dirs) {
626 dirs[0] = testDirs[1];
627 dirs[1] = testDirs[0];
628 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000629 return true;
630 }
631 }
632 return false;
633}
634
Mike Reed0c3137c2018-02-20 13:57:05 -0500635bool SkPath::isOval(SkRect* bounds) const {
636 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
637}
638
639bool SkPath::isRRect(SkRRect* rrect) const {
640 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
641}
642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643int SkPath::countPoints() const {
644 return fPathRef->countPoints();
645}
646
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000647int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 SkDEBUGCODE(this->validate();)
649
650 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000651 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000652 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800653 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655}
656
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000657SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
659 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000660 }
661 return SkPoint::Make(0, 0);
662}
663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664int SkPath::countVerbs() const {
665 return fPathRef->countVerbs();
666}
667
668static inline void copy_verbs_reverse(uint8_t* inorderDst,
669 const uint8_t* reversedSrc,
670 int count) {
671 for (int i = 0; i < count; ++i) {
672 inorderDst[i] = reversedSrc[~i];
673 }
674}
675
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000676int SkPath::getVerbs(uint8_t dst[], int max) const {
677 SkDEBUGCODE(this->validate();)
678
679 SkASSERT(max >= 0);
680 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = SkMin32(max, fPathRef->countVerbs());
682 copy_verbs_reverse(dst, fPathRef->verbs(), count);
683 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000684}
685
reed@google.com294dd7b2011-10-11 11:58:32 +0000686bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 SkDEBUGCODE(this->validate();)
688
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000690 if (count > 0) {
691 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000694 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 if (lastPt) {
697 lastPt->set(0, 0);
698 }
699 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
caryclarkaec25102015-04-29 08:28:30 -0700702void SkPath::setPt(int index, SkScalar x, SkScalar y) {
703 SkDEBUGCODE(this->validate();)
704
705 int count = fPathRef->countPoints();
706 if (count <= index) {
707 return;
708 } else {
709 SkPathRef::Editor ed(&fPathRef);
710 ed.atPoint(index)->set(x, y);
711 }
712}
713
reed@android.com8a1c16f2008-12-17 15:59:43 +0000714void SkPath::setLastPt(SkScalar x, SkScalar y) {
715 SkDEBUGCODE(this->validate();)
716
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000717 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 if (count == 0) {
719 this->moveTo(x, y);
720 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000721 SkPathRef::Editor ed(&fPathRef);
722 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000723 }
724}
725
reed@google.com04863fa2011-05-15 04:08:24 +0000726void SkPath::setConvexity(Convexity c) {
727 if (fConvexity != c) {
728 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000729 }
730}
731
reed@android.com8a1c16f2008-12-17 15:59:43 +0000732//////////////////////////////////////////////////////////////////////////////
733// Construction methods
734
reed026beb52015-06-10 14:23:15 -0700735#define DIRTY_AFTER_EDIT \
736 do { \
737 fConvexity = kUnknown_Convexity; \
738 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000739 } while (0)
740
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741void SkPath::incReserve(U16CPU inc) {
742 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000743 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000744 SkDEBUGCODE(this->validate();)
745}
746
747void SkPath::moveTo(SkScalar x, SkScalar y) {
748 SkDEBUGCODE(this->validate();)
749
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000752 // remember our index
753 fLastMoveToIndex = fPathRef->countPoints();
754
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000755 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700756
757 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000758}
759
760void SkPath::rMoveTo(SkScalar x, SkScalar y) {
761 SkPoint pt;
762 this->getLastPt(&pt);
763 this->moveTo(pt.fX + x, pt.fY + y);
764}
765
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000766void SkPath::injectMoveToIfNeeded() {
767 if (fLastMoveToIndex < 0) {
768 SkScalar x, y;
769 if (fPathRef->countVerbs() == 0) {
770 x = y = 0;
771 } else {
772 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
773 x = pt.fX;
774 y = pt.fY;
775 }
776 this->moveTo(x, y);
777 }
778}
779
reed@android.com8a1c16f2008-12-17 15:59:43 +0000780void SkPath::lineTo(SkScalar x, SkScalar y) {
781 SkDEBUGCODE(this->validate();)
782
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000783 this->injectMoveToIfNeeded();
784
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000785 SkPathRef::Editor ed(&fPathRef);
786 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787
reed@google.comb54455e2011-05-16 14:16:04 +0000788 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789}
790
791void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000792 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000793 SkPoint pt;
794 this->getLastPt(&pt);
795 this->lineTo(pt.fX + x, pt.fY + y);
796}
797
798void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
799 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000800
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000801 this->injectMoveToIfNeeded();
802
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000803 SkPathRef::Editor ed(&fPathRef);
804 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 pts[0].set(x1, y1);
806 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000807
reed@google.comb54455e2011-05-16 14:16:04 +0000808 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809}
810
811void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000812 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 SkPoint pt;
814 this->getLastPt(&pt);
815 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
816}
817
reed@google.com277c3f82013-05-31 15:17:50 +0000818void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
819 SkScalar w) {
820 // check for <= 0 or NaN with this test
821 if (!(w > 0)) {
822 this->lineTo(x2, y2);
823 } else if (!SkScalarIsFinite(w)) {
824 this->lineTo(x1, y1);
825 this->lineTo(x2, y2);
826 } else if (SK_Scalar1 == w) {
827 this->quadTo(x1, y1, x2, y2);
828 } else {
829 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000830
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000831 this->injectMoveToIfNeeded();
832
reed@google.com277c3f82013-05-31 15:17:50 +0000833 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000834 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000835 pts[0].set(x1, y1);
836 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000837
reed@google.com277c3f82013-05-31 15:17:50 +0000838 DIRTY_AFTER_EDIT;
839 }
840}
841
842void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
843 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000844 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000845 SkPoint pt;
846 this->getLastPt(&pt);
847 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
848}
849
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
851 SkScalar x3, SkScalar y3) {
852 SkDEBUGCODE(this->validate();)
853
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000854 this->injectMoveToIfNeeded();
855
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 SkPathRef::Editor ed(&fPathRef);
857 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 pts[0].set(x1, y1);
859 pts[1].set(x2, y2);
860 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000861
reed@google.comb54455e2011-05-16 14:16:04 +0000862 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863}
864
865void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
866 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000867 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 SkPoint pt;
869 this->getLastPt(&pt);
870 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
871 pt.fX + x3, pt.fY + y3);
872}
873
874void SkPath::close() {
875 SkDEBUGCODE(this->validate();)
876
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880 case kLine_Verb:
881 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000882 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000883 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000884 case kMove_Verb: {
885 SkPathRef::Editor ed(&fPathRef);
886 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000888 }
reed@google.com277c3f82013-05-31 15:17:50 +0000889 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000890 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000891 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000892 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000893 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000894 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000895 }
896 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000897
898 // signal that we need a moveTo to follow us (unless we're done)
899#if 0
900 if (fLastMoveToIndex >= 0) {
901 fLastMoveToIndex = ~fLastMoveToIndex;
902 }
903#else
904 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
905#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000906}
907
908///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000909
fmalitac08d53e2015-11-17 09:53:29 -0800910namespace {
911
912template <unsigned N>
913class PointIterator {
914public:
915 PointIterator(SkPath::Direction dir, unsigned startIndex)
916 : fCurrent(startIndex % N)
917 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
918
919 const SkPoint& current() const {
920 SkASSERT(fCurrent < N);
921 return fPts[fCurrent];
922 }
923
924 const SkPoint& next() {
925 fCurrent = (fCurrent + fAdvance) % N;
926 return this->current();
927 }
928
929protected:
930 SkPoint fPts[N];
931
932private:
933 unsigned fCurrent;
934 unsigned fAdvance;
935};
936
937class RectPointIterator : public PointIterator<4> {
938public:
939 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
940 : PointIterator(dir, startIndex) {
941
942 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
943 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
944 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
945 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
946 }
947};
948
949class OvalPointIterator : public PointIterator<4> {
950public:
951 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
952 : PointIterator(dir, startIndex) {
953
954 const SkScalar cx = oval.centerX();
955 const SkScalar cy = oval.centerY();
956
957 fPts[0] = SkPoint::Make(cx, oval.fTop);
958 fPts[1] = SkPoint::Make(oval.fRight, cy);
959 fPts[2] = SkPoint::Make(cx, oval.fBottom);
960 fPts[3] = SkPoint::Make(oval.fLeft, cy);
961 }
962};
963
964class RRectPointIterator : public PointIterator<8> {
965public:
966 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
967 : PointIterator(dir, startIndex) {
968
969 const SkRect& bounds = rrect.getBounds();
970 const SkScalar L = bounds.fLeft;
971 const SkScalar T = bounds.fTop;
972 const SkScalar R = bounds.fRight;
973 const SkScalar B = bounds.fBottom;
974
975 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
976 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
977 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
978 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
979 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
980 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
981 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
982 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
983 }
984};
985
986} // anonymous namespace
987
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000988static void assert_known_direction(int dir) {
989 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
990}
991
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800993 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000994}
995
996void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
997 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800998 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
999}
1000
1001void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001002 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001003 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001004 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001005 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001006 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001007
fmalitac08d53e2015-11-17 09:53:29 -08001008 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001009
fmalitac08d53e2015-11-17 09:53:29 -08001010 const int kVerbs = 5; // moveTo + 3x lineTo + close
1011 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001012
fmalitac08d53e2015-11-17 09:53:29 -08001013 RectPointIterator iter(rect, dir, startIndex);
1014
1015 this->moveTo(iter.current());
1016 this->lineTo(iter.next());
1017 this->lineTo(iter.next());
1018 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001020
1021 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022}
1023
reed@google.com744faba2012-05-29 19:54:52 +00001024void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1025 SkDEBUGCODE(this->validate();)
1026 if (count <= 0) {
1027 return;
1028 }
1029
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001030 fLastMoveToIndex = fPathRef->countPoints();
1031
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032 // +close makes room for the extra kClose_Verb
1033 SkPathRef::Editor ed(&fPathRef, count+close, count);
1034
1035 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001036 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001037 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1038 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001039 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001040
reed@google.com744faba2012-05-29 19:54:52 +00001041 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001042 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001043 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001044 }
1045
reed@google.com744faba2012-05-29 19:54:52 +00001046 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001047 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001048}
1049
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050#include "SkGeometry.h"
1051
reedf90ea012015-01-29 12:03:58 -08001052static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1053 SkPoint* pt) {
1054 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055 // Chrome uses this path to move into and out of ovals. If not
1056 // treated as a special case the moves can distort the oval's
1057 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001058 pt->set(oval.fRight, oval.centerY());
1059 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060 } else if (0 == oval.width() && 0 == oval.height()) {
1061 // Chrome will sometimes create 0 radius round rects. Having degenerate
1062 // quad segments in the path prevents the path from being recognized as
1063 // a rect.
1064 // TODO: optimizing the case where only one of width or height is zero
1065 // should also be considered. This case, however, doesn't seem to be
1066 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001067 pt->set(oval.fRight, oval.fTop);
1068 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001069 }
reedf90ea012015-01-29 12:03:58 -08001070 return false;
1071}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001072
reedd5d27d92015-02-09 13:54:43 -08001073// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1074//
1075static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1076 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1077 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1078 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079
1080 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001081 loss in radians-conversion and/or sin/cos, we may end up with coincident
1082 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1083 of drawing a nearly complete circle (good).
1084 e.g. canvas.drawArc(0, 359.99, ...)
1085 -vs- canvas.drawArc(0, 359.9, ...)
1086 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001087 */
reedd5d27d92015-02-09 13:54:43 -08001088 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089 SkScalar sw = SkScalarAbs(sweepAngle);
1090 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1091 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1092 // make a guess at a tiny angle (in radians) to tweak by
1093 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1094 // not sure how much will be enough, so we use a loop
1095 do {
1096 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001097 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1098 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099 }
1100 }
reedd5d27d92015-02-09 13:54:43 -08001101 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1102}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001103
reed9e779d42015-02-17 11:43:14 -08001104/**
1105 * If this returns 0, then the caller should just line-to the singlePt, else it should
1106 * ignore singlePt and append the specified number of conics.
1107 */
reedd5d27d92015-02-09 13:54:43 -08001108static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001109 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1110 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001111 SkMatrix matrix;
1112
1113 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1114 matrix.postTranslate(oval.centerX(), oval.centerY());
1115
reed9e779d42015-02-17 11:43:14 -08001116 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1117 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001118 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001119 }
1120 return count;
reedd5d27d92015-02-09 13:54:43 -08001121}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001122
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001123void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001125 SkRRect rrect;
1126 rrect.setRectRadii(rect, (const SkVector*) radii);
1127 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001128}
1129
reed@google.com4ed0fb72012-12-12 20:48:18 +00001130void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001131 // legacy start indices: 6 (CW) and 7(CCW)
1132 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1133}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001134
fmalitac08d53e2015-11-17 09:53:29 -08001135void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1136 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001137
caryclarkda707bf2015-11-19 14:47:43 -08001138 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001139 const SkRect& bounds = rrect.getBounds();
1140
Brian Salomon0a241ce2017-12-15 11:31:05 -05001141 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001142 // degenerate(rect) => radii points are collapsing
1143 this->addRect(bounds, dir, (startIndex + 1) / 2);
1144 } else if (rrect.isOval()) {
1145 // degenerate(oval) => line points are collapsing
1146 this->addOval(bounds, dir, startIndex / 2);
1147 } else {
1148 fFirstDirection = this->hasOnlyMoveTos() ?
1149 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1150
1151 SkAutoPathBoundsUpdate apbu(this, bounds);
1152 SkAutoDisableDirectionCheck addc(this);
1153
1154 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1155 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1156 const SkScalar weight = SK_ScalarRoot2Over2;
1157
1158 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1159 const int kVerbs = startsWithConic
1160 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1161 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1162 this->incReserve(kVerbs);
1163
1164 RRectPointIterator rrectIter(rrect, dir, startIndex);
1165 // Corner iterator indices follow the collapsed radii model,
1166 // adjusted such that the start pt is "behind" the radii start pt.
1167 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1168 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1169
1170 this->moveTo(rrectIter.current());
1171 if (startsWithConic) {
1172 for (unsigned i = 0; i < 3; ++i) {
1173 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1174 this->lineTo(rrectIter.next());
1175 }
1176 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1177 // final lineTo handled by close().
1178 } else {
1179 for (unsigned i = 0; i < 4; ++i) {
1180 this->lineTo(rrectIter.next());
1181 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1182 }
1183 }
1184 this->close();
1185
caryclarkda707bf2015-11-19 14:47:43 -08001186 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001187 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001188
fmalitac08d53e2015-11-17 09:53:29 -08001189 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1190 }
1191
1192 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001193}
1194
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001195bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001196 int count = fPathRef->countVerbs();
1197 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1198 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001199 if (*verbs == kLine_Verb ||
1200 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001201 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001202 *verbs == kCubic_Verb) {
1203 return false;
1204 }
1205 ++verbs;
1206 }
1207 return true;
1208}
1209
Brian Osmana2318572017-07-10 15:09:26 -04001210bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1211 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001212 if (count < 2) {
1213 return true;
1214 }
Brian Osmana2318572017-07-10 15:09:26 -04001215 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001216 const SkPoint& first = *pts;
1217 for (int index = 1; index < count; ++index) {
1218 if (first != pts[index]) {
1219 return false;
1220 }
1221 }
1222 return true;
1223}
1224
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001225void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1226 Direction dir) {
1227 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001228
humper@google.com75e3ca12013-04-08 21:44:11 +00001229 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001230 return;
1231 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001232
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001233 SkRRect rrect;
1234 rrect.setRectXY(rect, rx, ry);
1235 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001236}
1237
reed@android.com8a1c16f2008-12-17 15:59:43 +00001238void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001239 // legacy start index: 1
1240 this->addOval(oval, dir, 1);
1241}
1242
1243void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001244 assert_known_direction(dir);
1245
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001246 /* If addOval() is called after previous moveTo(),
1247 this path is still marked as an oval. This is used to
1248 fit into WebKit's calling sequences.
1249 We can't simply check isEmpty() in this case, as additional
1250 moveTo() would mark the path non empty.
1251 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001252 bool isOval = hasOnlyMoveTos();
1253 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001254 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001255 } else {
reed026beb52015-06-10 14:23:15 -07001256 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001257 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001258
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001259 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 SkAutoPathBoundsUpdate apbu(this, oval);
1261
fmalitac08d53e2015-11-17 09:53:29 -08001262 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1263 const int kVerbs = 6; // moveTo + 4x conicTo + close
1264 this->incReserve(kVerbs);
1265
1266 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1267 // The corner iterator pts are tracking "behind" the oval/radii pts.
1268 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001269 const SkScalar weight = SK_ScalarRoot2Over2;
1270
fmalitac08d53e2015-11-17 09:53:29 -08001271 this->moveTo(ovalIter.current());
1272 for (unsigned i = 0; i < 4; ++i) {
1273 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001274 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276
fmalitac08d53e2015-11-17 09:53:29 -08001277 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1278
robertphillips@google.com466310d2013-12-03 16:43:54 +00001279 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001280
bsalomon78d58d12016-05-27 09:17:04 -07001281 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001282}
1283
reed@android.com8a1c16f2008-12-17 15:59:43 +00001284void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1285 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001286 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001287 }
1288}
1289
reed@android.com8a1c16f2008-12-17 15:59:43 +00001290void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1291 bool forceMoveTo) {
1292 if (oval.width() < 0 || oval.height() < 0) {
1293 return;
1294 }
1295
reedf90ea012015-01-29 12:03:58 -08001296 if (fPathRef->countVerbs() == 0) {
1297 forceMoveTo = true;
1298 }
1299
1300 SkPoint lonePt;
1301 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1302 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1303 return;
1304 }
1305
reedd5d27d92015-02-09 13:54:43 -08001306 SkVector startV, stopV;
1307 SkRotationDirection dir;
1308 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1309
reed9e779d42015-02-17 11:43:14 -08001310 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001311
1312 // At this point, we know that the arc is not a lone point, but startV == stopV
1313 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1314 // cannot handle it.
1315 if (startV == stopV) {
1316 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1317 SkScalar radiusX = oval.width() / 2;
1318 SkScalar radiusY = oval.height() / 2;
1319 // We cannot use SkScalarSinCos function in the next line because
1320 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1321 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001322 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001323 // make sin(endAngle) to be 0 which will then draw a dot.
1324 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1325 oval.centerY() + radiusY * sk_float_sin(endAngle));
1326 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1327 return;
1328 }
1329
reedd5d27d92015-02-09 13:54:43 -08001330 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001331 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001332 if (count) {
1333 this->incReserve(count * 2 + 1);
1334 const SkPoint& pt = conics[0].fPts[0];
1335 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1336 for (int i = 0; i < count; ++i) {
1337 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1338 }
reed9e779d42015-02-17 11:43:14 -08001339 } else {
1340 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001341 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342}
1343
caryclark55d49052016-01-23 05:07:04 -08001344// This converts the SVG arc to conics.
1345// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1346// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1347// See also SVG implementation notes:
1348// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1349// Note that arcSweep bool value is flipped from the original implementation.
1350void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1351 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001352 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001353 SkPoint srcPts[2];
1354 this->getLastPt(&srcPts[0]);
1355 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1356 // joining the endpoints.
1357 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1358 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001359 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001360 return;
1361 }
1362 // If the current point and target point for the arc are identical, it should be treated as a
1363 // zero length path. This ensures continuity in animations.
1364 srcPts[1].set(x, y);
1365 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001366 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001367 return;
1368 }
1369 rx = SkScalarAbs(rx);
1370 ry = SkScalarAbs(ry);
1371 SkVector midPointDistance = srcPts[0] - srcPts[1];
1372 midPointDistance *= 0.5f;
1373
1374 SkMatrix pointTransform;
1375 pointTransform.setRotate(-angle);
1376
1377 SkPoint transformedMidPoint;
1378 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1379 SkScalar squareRx = rx * rx;
1380 SkScalar squareRy = ry * ry;
1381 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1382 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1383
1384 // Check if the radii are big enough to draw the arc, scale radii if not.
1385 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1386 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1387 if (radiiScale > 1) {
1388 radiiScale = SkScalarSqrt(radiiScale);
1389 rx *= radiiScale;
1390 ry *= radiiScale;
1391 }
1392
1393 pointTransform.setScale(1 / rx, 1 / ry);
1394 pointTransform.preRotate(-angle);
1395
1396 SkPoint unitPts[2];
1397 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1398 SkVector delta = unitPts[1] - unitPts[0];
1399
1400 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1401 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1402
1403 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1404 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1405 scaleFactor = -scaleFactor;
1406 }
1407 delta.scale(scaleFactor);
1408 SkPoint centerPoint = unitPts[0] + unitPts[1];
1409 centerPoint *= 0.5f;
1410 centerPoint.offset(-delta.fY, delta.fX);
1411 unitPts[0] -= centerPoint;
1412 unitPts[1] -= centerPoint;
1413 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1414 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1415 SkScalar thetaArc = theta2 - theta1;
1416 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1417 thetaArc += SK_ScalarPI * 2;
1418 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1419 thetaArc -= SK_ScalarPI * 2;
1420 }
1421 pointTransform.setRotate(angle);
1422 pointTransform.preScale(rx, ry);
1423
Cary Clark1acd3c72017-12-08 11:37:01 -05001424#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001425 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001426#else
1427 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1428 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1429#endif
caryclark55d49052016-01-23 05:07:04 -08001430 SkScalar thetaWidth = thetaArc / segments;
1431 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1432 if (!SkScalarIsFinite(t)) {
1433 return;
1434 }
1435 SkScalar startTheta = theta1;
1436 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001437#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1438 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1439 return scalar == SkScalarFloorToScalar(scalar);
1440 };
1441 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1442 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1443 scalar_is_integer(x) && scalar_is_integer(y);
1444#endif
caryclark55d49052016-01-23 05:07:04 -08001445 for (int i = 0; i < segments; ++i) {
1446 SkScalar endTheta = startTheta + thetaWidth;
1447 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1448
1449 unitPts[1].set(cosEndTheta, sinEndTheta);
1450 unitPts[1] += centerPoint;
1451 unitPts[0] = unitPts[1];
1452 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1453 SkPoint mapped[2];
1454 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001455 /*
1456 Computing the arc width introduces rounding errors that cause arcs to start
1457 outside their marks. A round rect may lose convexity as a result. If the input
1458 values are on integers, place the conic on integers as well.
1459 */
1460#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1461 if (expectIntegers) {
1462 SkScalar* mappedScalars = &mapped[0].fX;
1463 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1464 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1465 }
1466 }
1467#endif
caryclark55d49052016-01-23 05:07:04 -08001468 this->conicTo(mapped[0], mapped[1], w);
1469 startTheta = endTheta;
1470 }
1471}
1472
1473void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1474 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1475 SkPoint currentPoint;
1476 this->getLastPt(&currentPoint);
1477 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1478}
1479
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001480void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 if (oval.isEmpty() || 0 == sweepAngle) {
1482 return;
1483 }
1484
1485 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1486
1487 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001488 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1489 // See SkPath::addOval() docs.
1490 SkScalar startOver90 = startAngle / 90.f;
1491 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1492 SkScalar error = startOver90 - startOver90I;
1493 if (SkScalarNearlyEqual(error, 0)) {
1494 // Index 1 is at startAngle == 0.
1495 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1496 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1497 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1498 (unsigned) startIndex);
1499 return;
1500 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 }
bsalomon1978ce22016-05-31 10:42:16 -07001502 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001503}
1504
1505/*
1506 Need to handle the case when the angle is sharp, and our computed end-points
1507 for the arc go behind pt1 and/or p2...
1508*/
reedc7789042015-01-29 12:59:11 -08001509void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001510 if (radius == 0) {
1511 this->lineTo(x1, y1);
1512 return;
1513 }
1514
1515 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001516
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 // need to know our prev pt so we can construct tangent vectors
1518 {
1519 SkPoint start;
1520 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001521 // Handle degenerate cases by adding a line to the first point and
1522 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001523 before.setNormalize(x1 - start.fX, y1 - start.fY);
1524 after.setNormalize(x2 - x1, y2 - y1);
1525 }
reed@google.comabf15c12011-01-18 20:35:51 +00001526
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 SkScalar cosh = SkPoint::DotProduct(before, after);
1528 SkScalar sinh = SkPoint::CrossProduct(before, after);
1529
1530 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001531 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 return;
1533 }
reed@google.comabf15c12011-01-18 20:35:51 +00001534
Mike Reeda99b6ce2017-02-04 11:04:26 -05001535 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536
Mike Reeda99b6ce2017-02-04 11:04:26 -05001537 SkScalar xx = x1 - dist * before.fX;
1538 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001539 after.setLength(dist);
1540 this->lineTo(xx, yy);
1541 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1542 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543}
1544
1545///////////////////////////////////////////////////////////////////////////////
1546
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001547void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 SkMatrix matrix;
1549
1550 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001551 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001552}
1553
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001554void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001555 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001557 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 SkPoint pts[4];
1559 Verb verb;
1560
Cary Clark9480d822017-10-19 18:01:13 -04001561 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001562 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001563 while ((verb = iter.next(pts)) != kDone_Verb) {
1564 switch (verb) {
1565 case kMove_Verb:
1566 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001567 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1568 injectMoveToIfNeeded(); // In case last contour is closed
1569 this->lineTo(pts[0]);
1570 } else {
1571 this->moveTo(pts[0]);
1572 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 break;
1574 case kLine_Verb:
1575 proc(matrix, &pts[1], &pts[1], 1);
1576 this->lineTo(pts[1]);
1577 break;
1578 case kQuad_Verb:
1579 proc(matrix, &pts[1], &pts[1], 2);
1580 this->quadTo(pts[1], pts[2]);
1581 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001582 case kConic_Verb:
1583 proc(matrix, &pts[1], &pts[1], 2);
1584 this->conicTo(pts[1], pts[2], iter.conicWeight());
1585 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001586 case kCubic_Verb:
1587 proc(matrix, &pts[1], &pts[1], 3);
1588 this->cubicTo(pts[1], pts[2], pts[3]);
1589 break;
1590 case kClose_Verb:
1591 this->close();
1592 break;
1593 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001594 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001596 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001597 }
1598}
1599
1600///////////////////////////////////////////////////////////////////////////////
1601
reed@google.com277c3f82013-05-31 15:17:50 +00001602static int pts_in_verb(unsigned verb) {
1603 static const uint8_t gPtsInVerb[] = {
1604 1, // kMove
1605 1, // kLine
1606 2, // kQuad
1607 2, // kConic
1608 3, // kCubic
1609 0, // kClose
1610 0 // kDone
1611 };
1612
1613 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1614 return gPtsInVerb[verb];
1615}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001616
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617// ignore the last point of the 1st contour
1618void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001619 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1620 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001621 return;
1622 }
caryclark51c56782016-11-07 05:09:28 -08001623 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1624 SkASSERT(verbsEnd[0] == kMove_Verb);
1625 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1626 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627
caryclark51c56782016-11-07 05:09:28 -08001628 while (verbs < verbsEnd) {
1629 uint8_t v = *verbs++;
1630 pts -= pts_in_verb(v);
1631 switch (v) {
1632 case kMove_Verb:
1633 // if the path has multiple contours, stop after reversing the last
1634 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001636 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 break;
1638 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001639 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001640 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001641 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001642 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001643 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001645 this->cubicTo(pts[2], pts[1], pts[0]);
1646 break;
1647 case kClose_Verb:
1648 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 break;
1650 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001651 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001652 break;
1653 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 }
1655}
1656
reed@google.com63d73742012-01-10 15:33:12 +00001657void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001658 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 const SkPoint* pts = src.fPathRef->pointsEnd();
1661 // we will iterator through src's verbs backwards
1662 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1663 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001664 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001665
1666 bool needMove = true;
1667 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 while (verbs < verbsEnd) {
1669 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001670 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001671
1672 if (needMove) {
1673 --pts;
1674 this->moveTo(pts->fX, pts->fY);
1675 needMove = false;
1676 }
1677 pts -= n;
1678 switch (v) {
1679 case kMove_Verb:
1680 if (needClose) {
1681 this->close();
1682 needClose = false;
1683 }
1684 needMove = true;
1685 pts += 1; // so we see the point in "if (needMove)" above
1686 break;
1687 case kLine_Verb:
1688 this->lineTo(pts[0]);
1689 break;
1690 case kQuad_Verb:
1691 this->quadTo(pts[1], pts[0]);
1692 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001693 case kConic_Verb:
1694 this->conicTo(pts[1], pts[0], *--conicWeights);
1695 break;
reed@google.com63d73742012-01-10 15:33:12 +00001696 case kCubic_Verb:
1697 this->cubicTo(pts[2], pts[1], pts[0]);
1698 break;
1699 case kClose_Verb:
1700 needClose = true;
1701 break;
1702 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001703 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001704 }
1705 }
1706}
1707
reed@android.com8a1c16f2008-12-17 15:59:43 +00001708///////////////////////////////////////////////////////////////////////////////
1709
1710void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1711 SkMatrix matrix;
1712
1713 matrix.setTranslate(dx, dy);
1714 this->transform(matrix, dst);
1715}
1716
reed@android.com8a1c16f2008-12-17 15:59:43 +00001717static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1718 int level = 2) {
1719 if (--level >= 0) {
1720 SkPoint tmp[7];
1721
1722 SkChopCubicAtHalf(pts, tmp);
1723 subdivide_cubic_to(path, &tmp[0], level);
1724 subdivide_cubic_to(path, &tmp[3], level);
1725 } else {
1726 path->cubicTo(pts[1], pts[2], pts[3]);
1727 }
1728}
1729
1730void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1731 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001732 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733 dst = (SkPath*)this;
1734 }
1735
tomhudson@google.com8d430182011-06-06 19:11:19 +00001736 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 SkPath tmp;
1738 tmp.fFillType = fFillType;
1739
1740 SkPath::Iter iter(*this, false);
1741 SkPoint pts[4];
1742 SkPath::Verb verb;
1743
reed@google.com4a3b7142012-05-16 17:16:46 +00001744 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 switch (verb) {
1746 case kMove_Verb:
1747 tmp.moveTo(pts[0]);
1748 break;
1749 case kLine_Verb:
1750 tmp.lineTo(pts[1]);
1751 break;
1752 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001753 // promote the quad to a conic
1754 tmp.conicTo(pts[1], pts[2],
1755 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001757 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001758 tmp.conicTo(pts[1], pts[2],
1759 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001760 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 case kCubic_Verb:
1762 subdivide_cubic_to(&tmp, pts);
1763 break;
1764 case kClose_Verb:
1765 tmp.close();
1766 break;
1767 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001768 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 break;
1770 }
1771 }
1772
1773 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001774 SkPathRef::Editor ed(&dst->fPathRef);
1775 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001776 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1779
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001782 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001783 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001785
reed026beb52015-06-10 14:23:15 -07001786 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1787 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001788 } else {
1789 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001790 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1791 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001793 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1794 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001795 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001796 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001797 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001798 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001799 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001800 }
1801 }
1802
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803 SkDEBUGCODE(dst->validate();)
1804 }
1805}
1806
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807///////////////////////////////////////////////////////////////////////////////
1808///////////////////////////////////////////////////////////////////////////////
1809
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001810enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001811 kEmptyContour_SegmentState, // The current contour is empty. We may be
1812 // starting processing or we may have just
1813 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1815 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1816 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817};
1818
1819SkPath::Iter::Iter() {
1820#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001821 fPts = nullptr;
1822 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001825 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001826#endif
1827 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001828 fVerbs = nullptr;
1829 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830 fNeedClose = false;
1831}
1832
1833SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1834 this->setPath(path, forceClose);
1835}
1836
1837void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001838 fPts = path.fPathRef->points();
1839 fVerbs = path.fPathRef->verbs();
1840 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001841 fConicWeights = path.fPathRef->conicWeights();
1842 if (fConicWeights) {
1843 fConicWeights -= 1; // begin one behind
1844 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001846 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001847 fForceClose = SkToU8(forceClose);
1848 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001849 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850}
1851
1852bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001853 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 return false;
1855 }
1856 if (fForceClose) {
1857 return true;
1858 }
1859
1860 const uint8_t* verbs = fVerbs;
1861 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001862
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001863 if (kMove_Verb == *(verbs - 1)) {
1864 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865 }
1866
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001867 while (verbs > stop) {
1868 // verbs points one beyond the current verb, decrement first.
1869 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001870 if (kMove_Verb == v) {
1871 break;
1872 }
1873 if (kClose_Verb == v) {
1874 return true;
1875 }
1876 }
1877 return false;
1878}
1879
1880SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001881 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001882 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001883 // A special case: if both points are NaN, SkPoint::operation== returns
1884 // false, but the iterator expects that they are treated as the same.
1885 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001886 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1887 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001888 return kClose_Verb;
1889 }
1890
reed@google.com9e25dbf2012-05-15 17:05:38 +00001891 pts[0] = fLastPt;
1892 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001893 fLastPt = fMoveTo;
1894 fCloseLine = true;
1895 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001896 } else {
1897 pts[0] = fMoveTo;
1898 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900}
1901
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 if (fSegmentState == kAfterMove_SegmentState) {
1904 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001906 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001908 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1909 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001910 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001911 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001912}
1913
caryclarke8c56662015-07-14 11:19:26 -07001914void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 // We need to step over anything that will not move the current draw point
1916 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001917 const uint8_t* lastMoveVerb = nullptr;
1918 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001919 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 SkPoint lastPt = fLastPt;
1921 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 switch (verb) {
1924 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 // Keep a record of this most recent move
1926 lastMoveVerb = fVerbs;
1927 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001928 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001929 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001931 fPts++;
1932 break;
1933
1934 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001935 // A close when we are in a segment is always valid except when it
1936 // follows a move which follows a segment.
1937 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 return;
1939 }
1940 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001941 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 break;
1943
1944 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001945 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001946 if (lastMoveVerb) {
1947 fVerbs = lastMoveVerb;
1948 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001949 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 return;
1951 }
1952 return;
1953 }
1954 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001955 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 fPts++;
1957 break;
1958
reed@google.com277c3f82013-05-31 15:17:50 +00001959 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001961 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001962 if (lastMoveVerb) {
1963 fVerbs = lastMoveVerb;
1964 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001965 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 return;
1967 }
1968 return;
1969 }
1970 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001971 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001973 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 break;
1975
1976 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001977 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001978 if (lastMoveVerb) {
1979 fVerbs = lastMoveVerb;
1980 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001981 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 return;
1983 }
1984 return;
1985 }
1986 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001987 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 fPts += 3;
1989 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001990
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001992 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 }
1994 }
1995}
1996
reed@google.com4a3b7142012-05-16 17:16:46 +00001997SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001998 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001999
reed@android.com8a1c16f2008-12-17 15:59:43 +00002000 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002001 // Close the curve if requested and if there is some curve to close
2002 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002003 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002004 return kLine_Verb;
2005 }
2006 fNeedClose = false;
2007 return kClose_Verb;
2008 }
2009 return kDone_Verb;
2010 }
2011
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002012 // fVerbs is one beyond the current verb, decrement first
2013 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002014 const SkPoint* SK_RESTRICT srcPts = fPts;
2015 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016
2017 switch (verb) {
2018 case kMove_Verb:
2019 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002020 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002021 verb = this->autoClose(pts);
2022 if (verb == kClose_Verb) {
2023 fNeedClose = false;
2024 }
2025 return (Verb)verb;
2026 }
2027 if (fVerbs == fVerbStop) { // might be a trailing moveto
2028 return kDone_Verb;
2029 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002030 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002031 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002033 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002034 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 fNeedClose = fForceClose;
2036 break;
2037 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002038 pts[0] = this->cons_moveTo();
2039 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040 fLastPt = srcPts[0];
2041 fCloseLine = false;
2042 srcPts += 1;
2043 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002044 case kConic_Verb:
2045 fConicWeights += 1;
2046 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002048 pts[0] = this->cons_moveTo();
2049 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 fLastPt = srcPts[1];
2051 srcPts += 2;
2052 break;
2053 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002054 pts[0] = this->cons_moveTo();
2055 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002056 fLastPt = srcPts[2];
2057 srcPts += 3;
2058 break;
2059 case kClose_Verb:
2060 verb = this->autoClose(pts);
2061 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002062 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063 } else {
2064 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002065 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002067 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002068 break;
2069 }
2070 fPts = srcPts;
2071 return (Verb)verb;
2072}
2073
2074///////////////////////////////////////////////////////////////////////////////
2075
Ben Wagner4d1955c2017-03-10 13:08:15 -05002076#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002077#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002078#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002079
reed@google.com51bbe752013-01-17 22:07:50 +00002080static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002081 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002082 str->append(label);
2083 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002084
reed@google.com51bbe752013-01-17 22:07:50 +00002085 const SkScalar* values = &pts[0].fX;
2086 count *= 2;
2087
2088 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002089 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002090 if (i < count - 1) {
2091 str->append(", ");
2092 }
2093 }
Mike Reed405b9d22018-01-09 15:11:08 -05002094 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002095 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002096 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002097 }
caryclark08fa28c2014-10-23 13:08:56 -07002098 str->append(");");
reede05fed02014-12-15 07:59:53 -08002099 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002100 str->append(" // ");
2101 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002102 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002103 if (i < count - 1) {
2104 str->append(", ");
2105 }
2106 }
2107 if (conicWeight >= 0) {
2108 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002109 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002110 }
2111 }
2112 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002113}
2114
caryclarke9562592014-09-15 09:26:09 -07002115void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002116 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117 Iter iter(*this, forceClose);
2118 SkPoint pts[4];
2119 Verb verb;
2120
reed@google.com51bbe752013-01-17 22:07:50 +00002121 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002122 char const * const gFillTypeStrs[] = {
2123 "Winding",
2124 "EvenOdd",
2125 "InverseWinding",
2126 "InverseEvenOdd",
2127 };
2128 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2129 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002130 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 switch (verb) {
2132 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002133 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134 break;
2135 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002136 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 break;
2138 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002139 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002141 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002142 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002143 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002145 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 break;
2147 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002148 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 break;
2150 default:
2151 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2152 verb = kDone_Verb; // stop the loop
2153 break;
2154 }
caryclark1049f122015-04-20 08:31:59 -07002155 if (!wStream && builder.size()) {
2156 SkDebugf("%s", builder.c_str());
2157 builder.reset();
2158 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 }
caryclark66a5d8b2014-06-24 08:30:15 -07002160 if (wStream) {
2161 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002162 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163}
2164
reed@android.come522ca52009-11-23 20:10:41 +00002165void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002166 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002167}
2168
2169void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002170 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002171}
2172
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002173
Cary Clark0461e9f2017-08-25 15:13:38 -04002174bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002175 if ((fFillType & ~3) != 0) {
2176 return false;
2177 }
reed@google.comabf15c12011-01-18 20:35:51 +00002178
djsollen@google.com077348c2012-10-22 20:23:32 +00002179#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002180 if (!fBoundsIsDirty) {
2181 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002182
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002183 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002184 if (SkToBool(fIsFinite) != isFinite) {
2185 return false;
2186 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002187
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002188 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002189 // if we're empty, fBounds may be empty but translated, so we can't
2190 // necessarily compare to bounds directly
2191 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2192 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002193 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2194 return false;
2195 }
reed@android.come522ca52009-11-23 20:10:41 +00002196 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002197 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002198 if (!fBounds.isEmpty()) {
2199 return false;
2200 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002201 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002202 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002203 if (!fBounds.contains(bounds)) {
2204 return false;
2205 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002206 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002207 }
reed@android.come522ca52009-11-23 20:10:41 +00002208 }
2209 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002210#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002211 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002212}
reed@android.come522ca52009-11-23 20:10:41 +00002213
reed@google.com04863fa2011-05-15 04:08:24 +00002214///////////////////////////////////////////////////////////////////////////////
2215
reed@google.com0b7b9822011-05-16 12:29:27 +00002216static int sign(SkScalar x) { return x < 0; }
2217#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002218
robertphillipsc506e302014-09-16 09:43:31 -07002219enum DirChange {
2220 kLeft_DirChange,
2221 kRight_DirChange,
2222 kStraight_DirChange,
2223 kBackwards_DirChange,
2224
2225 kInvalid_DirChange
2226};
2227
2228
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002229static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002230 // The error epsilon was empirically derived; worse case round rects
2231 // with a mid point outset by 2x float epsilon in tests had an error
2232 // of 12.
2233 const int epsilon = 16;
2234 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2235 return false;
2236 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002237 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002238 int aBits = SkFloatAs2sCompliment(compA);
2239 int bBits = SkFloatAs2sCompliment(compB);
2240 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002241}
2242
caryclarkb4216502015-03-02 13:02:34 -08002243static bool approximately_zero_when_compared_to(double x, double y) {
2244 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002245}
2246
caryclarkb4216502015-03-02 13:02:34 -08002247
reed@google.com04863fa2011-05-15 04:08:24 +00002248// only valid for a single contour
2249struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002250 Convexicator()
2251 : fPtCount(0)
2252 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002253 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002254 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002255 , fIsCurve(false)
2256 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002257 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002258 // warnings
djsollen2f124632016-04-29 13:53:05 -07002259 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002260 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002261 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002262 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002263 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002264
2265 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002266 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002267 }
2268
2269 SkPath::Convexity getConvexity() const { return fConvexity; }
2270
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002271 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002272 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002273
reed@google.com04863fa2011-05-15 04:08:24 +00002274 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002275 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002276 return;
2277 }
2278
2279 if (0 == fPtCount) {
2280 fCurrPt = pt;
2281 ++fPtCount;
2282 } else {
2283 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002284 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002285 if (!SkScalarIsFinite(lengthSqd)) {
2286 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002287 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002288 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002289 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002290 fCurrPt = pt;
2291 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002292 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002293 } else {
2294 SkASSERT(fPtCount > 2);
2295 this->addVec(vec);
2296 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002297
reed@google.com85b6e392011-05-15 20:25:17 +00002298 int sx = sign(vec.fX);
2299 int sy = sign(vec.fY);
2300 fDx += (sx != fSx);
2301 fDy += (sy != fSy);
2302 fSx = sx;
2303 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002304
reed@google.com85b6e392011-05-15 20:25:17 +00002305 if (fDx > 3 || fDy > 3) {
2306 fConvexity = SkPath::kConcave_Convexity;
2307 }
reed@google.com04863fa2011-05-15 04:08:24 +00002308 }
2309 }
2310 }
2311
2312 void close() {
2313 if (fPtCount > 2) {
2314 this->addVec(fFirstVec);
2315 }
2316 }
2317
caryclarkb4216502015-03-02 13:02:34 -08002318 DirChange directionChange(const SkVector& curVec) {
2319 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2320
2321 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2322 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2323 largest = SkTMax(largest, -smallest);
2324
2325 if (!almost_equal(largest, largest + cross)) {
2326 int sign = SkScalarSignAsInt(cross);
2327 if (sign) {
2328 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2329 }
2330 }
2331
2332 if (cross) {
2333 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2334 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2335 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2336 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2337 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2338 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2339 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2340 if (sign) {
2341 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2342 }
2343 }
2344 }
2345
Cary Clarkdf429f32017-11-08 11:44:31 -05002346 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2347 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2348 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2349 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002350 fLastVec.dot(curVec) < 0.0f) {
2351 return kBackwards_DirChange;
2352 }
2353
2354 return kStraight_DirChange;
2355 }
2356
Cary Clarkc8323aa2017-08-25 08:04:43 -04002357 bool hasBackwards() const {
2358 return fBackwards;
2359 }
caryclarkb4216502015-03-02 13:02:34 -08002360
caryclarkd3d1a982014-12-08 04:57:38 -08002361 bool isFinite() const {
2362 return fIsFinite;
2363 }
2364
caryclark5ccef572015-03-02 10:07:56 -08002365 void setCurve(bool isCurve) {
2366 fIsCurve = isCurve;
2367 }
2368
reed@google.com04863fa2011-05-15 04:08:24 +00002369private:
2370 void addVec(const SkVector& vec) {
2371 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002372 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002373 switch (dir) {
2374 case kLeft_DirChange: // fall through
2375 case kRight_DirChange:
2376 if (kInvalid_DirChange == fExpectedDir) {
2377 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002378 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2379 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002380 } else if (dir != fExpectedDir) {
2381 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002382 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002383 }
2384 fLastVec = vec;
2385 break;
2386 case kStraight_DirChange:
2387 break;
2388 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002389 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002390 // If any of the subsequent dir is non-backward, it'll be concave.
2391 // Otherwise, it's still convex.
2392 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002393 }
robertphillipsc506e302014-09-16 09:43:31 -07002394 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002395 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002396 break;
2397 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002398 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002399 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002400 }
2401 }
2402
caryclarkb4216502015-03-02 13:02:34 -08002403 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002404 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002405 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002406 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2407 // value with the current vec is deemed to be of a significant value.
2408 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002409 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002410 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002411 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002412 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002413 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002414 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002415 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002416 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002417};
2418
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002419SkPath::Convexity SkPath::internalGetConvexity() const {
2420 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002421 SkPoint pts[4];
2422 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002423 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002424
2425 int contourCount = 0;
2426 int count;
2427 Convexicator state;
2428
caryclarkd3d1a982014-12-08 04:57:38 -08002429 if (!isFinite()) {
2430 return kUnknown_Convexity;
2431 }
Brian Osman205a1262017-09-18 13:13:48 +00002432 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002433 switch (verb) {
2434 case kMove_Verb:
2435 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002436 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002437 return kConcave_Convexity;
2438 }
2439 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002440 // fall through
2441 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002442 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002443 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002444 break;
caryclark5ccef572015-03-02 10:07:56 -08002445 case kQuad_Verb:
2446 // fall through
2447 case kConic_Verb:
2448 // fall through
2449 case kCubic_Verb:
2450 count = 2 + (kCubic_Verb == verb);
2451 // As an additional enhancement, this could set curve true only
2452 // if the curve is nonlinear
2453 state.setCurve(true);
2454 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002455 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002456 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002457 state.close();
2458 count = 0;
2459 break;
2460 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002461 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002462 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002463 return kConcave_Convexity;
2464 }
2465
2466 for (int i = 1; i <= count; i++) {
2467 state.addPt(pts[i]);
2468 }
2469 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002470 if (!state.isFinite()) {
2471 return kUnknown_Convexity;
2472 }
reed@google.com04863fa2011-05-15 04:08:24 +00002473 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002474 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002475 return kConcave_Convexity;
2476 }
2477 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002478 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002479 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002480 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2481 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2482 fConvexity = Convexity::kConcave_Convexity;
2483 } else {
2484 fFirstDirection = state.getFirstDirection();
2485 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002486 }
2487 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002488}
reed@google.com69a99432012-01-10 18:00:10 +00002489
2490///////////////////////////////////////////////////////////////////////////////
2491
2492class ContourIter {
2493public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002494 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002495
2496 bool done() const { return fDone; }
2497 // if !done() then these may be called
2498 int count() const { return fCurrPtCount; }
2499 const SkPoint* pts() const { return fCurrPt; }
2500 void next();
2501
2502private:
2503 int fCurrPtCount;
2504 const SkPoint* fCurrPt;
2505 const uint8_t* fCurrVerb;
2506 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002507 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002508 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002509 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002510};
2511
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002512ContourIter::ContourIter(const SkPathRef& pathRef) {
2513 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002514 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002515 fCurrPt = pathRef.points();
2516 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002517 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002518 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002519 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002520 this->next();
2521}
2522
2523void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002524 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002525 fDone = true;
2526 }
2527 if (fDone) {
2528 return;
2529 }
2530
2531 // skip pts of prev contour
2532 fCurrPt += fCurrPtCount;
2533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002534 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002535 int ptCount = 1; // moveTo
2536 const uint8_t* verbs = fCurrVerb;
2537
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002538 for (--verbs; verbs > fStopVerbs; --verbs) {
2539 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002540 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002541 goto CONTOUR_END;
2542 case SkPath::kLine_Verb:
2543 ptCount += 1;
2544 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002545 case SkPath::kConic_Verb:
2546 fCurrConicWeight += 1;
2547 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002548 case SkPath::kQuad_Verb:
2549 ptCount += 2;
2550 break;
2551 case SkPath::kCubic_Verb:
2552 ptCount += 3;
2553 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002554 case SkPath::kClose_Verb:
2555 break;
2556 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002557 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002558 break;
2559 }
2560 }
2561CONTOUR_END:
2562 fCurrPtCount = ptCount;
2563 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002564 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002565}
2566
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002567// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002568static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002569 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2570 // We may get 0 when the above subtracts underflow. We expect this to be
2571 // very rare and lazily promote to double.
2572 if (0 == cross) {
2573 double p0x = SkScalarToDouble(p0.fX);
2574 double p0y = SkScalarToDouble(p0.fY);
2575
2576 double p1x = SkScalarToDouble(p1.fX);
2577 double p1y = SkScalarToDouble(p1.fY);
2578
2579 double p2x = SkScalarToDouble(p2.fX);
2580 double p2y = SkScalarToDouble(p2.fY);
2581
2582 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2583 (p1y - p0y) * (p2x - p0x));
2584
2585 }
2586 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002587}
2588
reed@google.comc1ea60a2012-01-31 15:15:36 +00002589// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002590static int find_max_y(const SkPoint pts[], int count) {
2591 SkASSERT(count > 0);
2592 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002593 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002594 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002595 SkScalar y = pts[i].fY;
2596 if (y > max) {
2597 max = y;
2598 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002599 }
2600 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002601 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002602}
2603
reed@google.comcabaf1d2012-01-11 21:03:05 +00002604static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2605 int i = index;
2606 for (;;) {
2607 i = (i + inc) % n;
2608 if (i == index) { // we wrapped around, so abort
2609 break;
2610 }
2611 if (pts[index] != pts[i]) { // found a different point, success!
2612 break;
2613 }
2614 }
2615 return i;
2616}
2617
reed@google.comc1ea60a2012-01-31 15:15:36 +00002618/**
2619 * Starting at index, and moving forward (incrementing), find the xmin and
2620 * xmax of the contiguous points that have the same Y.
2621 */
2622static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2623 int* maxIndexPtr) {
2624 const SkScalar y = pts[index].fY;
2625 SkScalar min = pts[index].fX;
2626 SkScalar max = min;
2627 int minIndex = index;
2628 int maxIndex = index;
2629 for (int i = index + 1; i < n; ++i) {
2630 if (pts[i].fY != y) {
2631 break;
2632 }
2633 SkScalar x = pts[i].fX;
2634 if (x < min) {
2635 min = x;
2636 minIndex = i;
2637 } else if (x > max) {
2638 max = x;
2639 maxIndex = i;
2640 }
2641 }
2642 *maxIndexPtr = maxIndex;
2643 return minIndex;
2644}
2645
reed026beb52015-06-10 14:23:15 -07002646static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2647 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002648}
2649
reed@google.comac8543f2012-01-30 20:51:25 +00002650/*
2651 * We loop through all contours, and keep the computed cross-product of the
2652 * contour that contained the global y-max. If we just look at the first
2653 * contour, we may find one that is wound the opposite way (correctly) since
2654 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2655 * that is outer most (or at least has the global y-max) before we can consider
2656 * its cross product.
2657 */
reed026beb52015-06-10 14:23:15 -07002658bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002659 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2660 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002661 return true;
2662 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002663
2664 // don't want to pay the cost for computing this if it
2665 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002666 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2667 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002668 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002669 return false;
2670 }
reed@google.com69a99432012-01-10 18:00:10 +00002671
reed026beb52015-06-10 14:23:15 -07002672 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002673
reed@google.comac8543f2012-01-30 20:51:25 +00002674 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002675 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002676 SkScalar ymaxCross = 0;
2677
reed@google.com69a99432012-01-10 18:00:10 +00002678 for (; !iter.done(); iter.next()) {
2679 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002680 if (n < 3) {
2681 continue;
2682 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002683
reed@google.comcabaf1d2012-01-11 21:03:05 +00002684 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002685 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002686 int index = find_max_y(pts, n);
2687 if (pts[index].fY < ymax) {
2688 continue;
2689 }
2690
2691 // If there is more than 1 distinct point at the y-max, we take the
2692 // x-min and x-max of them and just subtract to compute the dir.
2693 if (pts[(index + 1) % n].fY == pts[index].fY) {
2694 int maxIndex;
2695 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2696 if (minIndex == maxIndex) {
2697 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002698 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002699 SkASSERT(pts[minIndex].fY == pts[index].fY);
2700 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2701 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2702 // we just subtract the indices, and let that auto-convert to
2703 // SkScalar, since we just want - or + to signal the direction.
2704 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002705 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002706 TRY_CROSSPROD:
2707 // Find a next and prev index to use for the cross-product test,
2708 // but we try to find pts that form non-zero vectors from pts[index]
2709 //
2710 // Its possible that we can't find two non-degenerate vectors, so
2711 // we have to guard our search (e.g. all the pts could be in the
2712 // same place).
2713
2714 // we pass n - 1 instead of -1 so we don't foul up % operator by
2715 // passing it a negative LH argument.
2716 int prev = find_diff_pt(pts, index, n, n - 1);
2717 if (prev == index) {
2718 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002719 continue;
2720 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002721 int next = find_diff_pt(pts, index, n, 1);
2722 SkASSERT(next != index);
2723 cross = cross_prod(pts[prev], pts[index], pts[next]);
2724 // if we get a zero and the points are horizontal, then we look at the spread in
2725 // x-direction. We really should continue to walk away from the degeneracy until
2726 // there is a divergence.
2727 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2728 // construct the subtract so we get the correct Direction below
2729 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002730 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002731 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002732
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002733 if (cross) {
2734 // record our best guess so far
2735 ymax = pts[index].fY;
2736 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002737 }
2738 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002739 if (ymaxCross) {
2740 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002741 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002742 return true;
2743 } else {
2744 return false;
2745 }
reed@google.comac8543f2012-01-30 20:51:25 +00002746}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002747
2748///////////////////////////////////////////////////////////////////////////////
2749
caryclark9aacd902015-12-14 08:38:09 -08002750static bool between(SkScalar a, SkScalar b, SkScalar c) {
2751 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2752 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2753 return (a - b) * (c - b) <= 0;
2754}
2755
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002756static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2757 SkScalar t) {
2758 SkScalar A = c3 + 3*(c1 - c2) - c0;
2759 SkScalar B = 3*(c2 - c1 - c1 + c0);
2760 SkScalar C = 3*(c1 - c0);
2761 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002762 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763}
2764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765template <size_t N> static void find_minmax(const SkPoint pts[],
2766 SkScalar* minPtr, SkScalar* maxPtr) {
2767 SkScalar min, max;
2768 min = max = pts[0].fX;
2769 for (size_t i = 1; i < N; ++i) {
2770 min = SkMinScalar(min, pts[i].fX);
2771 max = SkMaxScalar(max, pts[i].fX);
2772 }
2773 *minPtr = min;
2774 *maxPtr = max;
2775}
2776
caryclark9cb5d752015-12-18 04:35:24 -08002777static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2778 if (start.fY == end.fY) {
2779 return between(start.fX, x, end.fX) && x != end.fX;
2780 } else {
2781 return x == start.fX && y == start.fY;
2782 }
2783}
2784
caryclark9aacd902015-12-14 08:38:09 -08002785static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002786 SkScalar y0 = pts[0].fY;
2787 SkScalar y3 = pts[3].fY;
2788
2789 int dir = 1;
2790 if (y0 > y3) {
2791 SkTSwap(y0, y3);
2792 dir = -1;
2793 }
2794 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795 return 0;
2796 }
caryclark9cb5d752015-12-18 04:35:24 -08002797 if (checkOnCurve(x, y, pts[0], pts[3])) {
2798 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002799 return 0;
2800 }
caryclark9cb5d752015-12-18 04:35:24 -08002801 if (y == y3) {
2802 return 0;
2803 }
2804
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805 // quickreject or quickaccept
2806 SkScalar min, max;
2807 find_minmax<4>(pts, &min, &max);
2808 if (x < min) {
2809 return 0;
2810 }
2811 if (x > max) {
2812 return dir;
2813 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002814
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002815 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002816 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002817 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002818 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002819 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002820 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002821 if (SkScalarNearlyEqual(xt, x)) {
2822 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2823 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002824 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002825 }
2826 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002827 return xt < x ? dir : 0;
2828}
2829
caryclark9aacd902015-12-14 08:38:09 -08002830static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 SkPoint dst[10];
2832 int n = SkChopCubicAtYExtrema(pts, dst);
2833 int w = 0;
2834 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002835 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 }
2837 return w;
2838}
2839
caryclark9aacd902015-12-14 08:38:09 -08002840static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2841 SkASSERT(src);
2842 SkASSERT(t >= 0 && t <= 1);
2843 SkScalar src2w = src[2] * w;
2844 SkScalar C = src[0];
2845 SkScalar A = src[4] - 2 * src2w + C;
2846 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002847 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002848}
2849
2850
2851static double conic_eval_denominator(SkScalar w, SkScalar t) {
2852 SkScalar B = 2 * (w - 1);
2853 SkScalar C = 1;
2854 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002855 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002856}
2857
2858static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2859 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002860 SkScalar y0 = pts[0].fY;
2861 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002862
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002863 int dir = 1;
2864 if (y0 > y2) {
2865 SkTSwap(y0, y2);
2866 dir = -1;
2867 }
caryclark9aacd902015-12-14 08:38:09 -08002868 if (y < y0 || y > y2) {
2869 return 0;
2870 }
caryclark9cb5d752015-12-18 04:35:24 -08002871 if (checkOnCurve(x, y, pts[0], pts[2])) {
2872 *onCurveCount += 1;
2873 return 0;
2874 }
caryclark9aacd902015-12-14 08:38:09 -08002875 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002876 return 0;
2877 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
caryclark9aacd902015-12-14 08:38:09 -08002879 SkScalar roots[2];
2880 SkScalar A = pts[2].fY;
2881 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2882 SkScalar C = pts[0].fY;
2883 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2884 B -= C; // B = b*w - w * yCept + yCept - a
2885 C -= y;
2886 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2887 SkASSERT(n <= 1);
2888 SkScalar xt;
2889 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002890 // zero roots are returned only when y0 == y
2891 // Need [0] if dir == 1
2892 // and [2] if dir == -1
2893 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002894 } else {
2895 SkScalar t = roots[0];
2896 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2897 }
2898 if (SkScalarNearlyEqual(xt, x)) {
2899 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2900 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002901 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002902 }
2903 }
2904 return xt < x ? dir : 0;
2905}
2906
2907static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2908 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2909 if (y0 == y1) {
2910 return true;
2911 }
2912 if (y0 < y1) {
2913 return y1 <= y2;
2914 } else {
2915 return y1 >= y2;
2916 }
2917}
2918
2919static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2920 int* onCurveCount) {
2921 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002922 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002923 // If the data points are very large, the conic may not be monotonic but may also
2924 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002925 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2926 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2927 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002928 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2929 }
2930 return w;
2931}
2932
2933static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2934 SkScalar y0 = pts[0].fY;
2935 SkScalar y2 = pts[2].fY;
2936
2937 int dir = 1;
2938 if (y0 > y2) {
2939 SkTSwap(y0, y2);
2940 dir = -1;
2941 }
2942 if (y < y0 || y > y2) {
2943 return 0;
2944 }
caryclark9cb5d752015-12-18 04:35:24 -08002945 if (checkOnCurve(x, y, pts[0], pts[2])) {
2946 *onCurveCount += 1;
2947 return 0;
2948 }
caryclark9aacd902015-12-14 08:38:09 -08002949 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002950 return 0;
2951 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002952 // bounds check on X (not required. is it faster?)
2953#if 0
2954 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2955 return 0;
2956 }
2957#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002958
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002959 SkScalar roots[2];
2960 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2961 2 * (pts[1].fY - pts[0].fY),
2962 pts[0].fY - y,
2963 roots);
2964 SkASSERT(n <= 1);
2965 SkScalar xt;
2966 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002967 // zero roots are returned only when y0 == y
2968 // Need [0] if dir == 1
2969 // and [2] if dir == -1
2970 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002971 } else {
2972 SkScalar t = roots[0];
2973 SkScalar C = pts[0].fX;
2974 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2975 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002976 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002977 }
caryclark9aacd902015-12-14 08:38:09 -08002978 if (SkScalarNearlyEqual(xt, x)) {
2979 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2980 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002981 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002982 }
2983 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002984 return xt < x ? dir : 0;
2985}
2986
caryclark9aacd902015-12-14 08:38:09 -08002987static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 SkPoint dst[5];
2989 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002990
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2992 n = SkChopQuadAtYExtrema(pts, dst);
2993 pts = dst;
2994 }
caryclark9aacd902015-12-14 08:38:09 -08002995 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002996 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002997 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 }
2999 return w;
3000}
3001
caryclark9aacd902015-12-14 08:38:09 -08003002static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 SkScalar x0 = pts[0].fX;
3004 SkScalar y0 = pts[0].fY;
3005 SkScalar x1 = pts[1].fX;
3006 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003007
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003009
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003010 int dir = 1;
3011 if (y0 > y1) {
3012 SkTSwap(y0, y1);
3013 dir = -1;
3014 }
caryclark9aacd902015-12-14 08:38:09 -08003015 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003016 return 0;
3017 }
caryclark9cb5d752015-12-18 04:35:24 -08003018 if (checkOnCurve(x, y, pts[0], pts[1])) {
3019 *onCurveCount += 1;
3020 return 0;
3021 }
3022 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003023 return 0;
3024 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003025 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003026
caryclark9aacd902015-12-14 08:38:09 -08003027 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003028 // zero cross means the point is on the line, and since the case where
3029 // y of the query point is at the end point is handled above, we can be
3030 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003031 if (x != x1 || y != pts[1].fY) {
3032 *onCurveCount += 1;
3033 }
caryclark9aacd902015-12-14 08:38:09 -08003034 dir = 0;
3035 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003036 dir = 0;
3037 }
3038 return dir;
3039}
3040
caryclark9aacd902015-12-14 08:38:09 -08003041static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3042 SkTDArray<SkVector>* tangents) {
3043 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3044 && !between(pts[2].fY, y, pts[3].fY)) {
3045 return;
3046 }
3047 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3048 && !between(pts[2].fX, x, pts[3].fX)) {
3049 return;
3050 }
3051 SkPoint dst[10];
3052 int n = SkChopCubicAtYExtrema(pts, dst);
3053 for (int i = 0; i <= n; ++i) {
3054 SkPoint* c = &dst[i * 3];
3055 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003056 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003057 continue;
mbarbella276e6332016-05-31 14:44:01 -07003058 }
caryclark9aacd902015-12-14 08:38:09 -08003059 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3060 if (!SkScalarNearlyEqual(x, xt)) {
3061 continue;
3062 }
3063 SkVector tangent;
3064 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3065 tangents->push(tangent);
3066 }
3067}
3068
3069static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3070 SkTDArray<SkVector>* tangents) {
3071 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3072 return;
3073 }
3074 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3075 return;
3076 }
3077 SkScalar roots[2];
3078 SkScalar A = pts[2].fY;
3079 SkScalar B = pts[1].fY * w - y * w + y;
3080 SkScalar C = pts[0].fY;
3081 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3082 B -= C; // B = b*w - w * yCept + yCept - a
3083 C -= y;
3084 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3085 for (int index = 0; index < n; ++index) {
3086 SkScalar t = roots[index];
3087 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3088 if (!SkScalarNearlyEqual(x, xt)) {
3089 continue;
3090 }
3091 SkConic conic(pts, w);
3092 tangents->push(conic.evalTangentAt(t));
3093 }
3094}
3095
3096static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3097 SkTDArray<SkVector>* tangents) {
3098 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3099 return;
3100 }
3101 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3102 return;
3103 }
3104 SkScalar roots[2];
3105 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3106 2 * (pts[1].fY - pts[0].fY),
3107 pts[0].fY - y,
3108 roots);
3109 for (int index = 0; index < n; ++index) {
3110 SkScalar t = roots[index];
3111 SkScalar C = pts[0].fX;
3112 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3113 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003114 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003115 if (!SkScalarNearlyEqual(x, xt)) {
3116 continue;
3117 }
3118 tangents->push(SkEvalQuadTangentAt(pts, t));
3119 }
3120}
3121
3122static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3123 SkTDArray<SkVector>* tangents) {
3124 SkScalar y0 = pts[0].fY;
3125 SkScalar y1 = pts[1].fY;
3126 if (!between(y0, y, y1)) {
3127 return;
3128 }
3129 SkScalar x0 = pts[0].fX;
3130 SkScalar x1 = pts[1].fX;
3131 if (!between(x0, x, x1)) {
3132 return;
3133 }
3134 SkScalar dx = x1 - x0;
3135 SkScalar dy = y1 - y0;
3136 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3137 return;
3138 }
3139 SkVector v;
3140 v.set(dx, dy);
3141 tangents->push(v);
3142}
3143
reed@google.com4db592c2013-10-30 17:39:43 +00003144static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3145 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3146}
3147
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003148bool SkPath::contains(SkScalar x, SkScalar y) const {
3149 bool isInverse = this->isInverseFillType();
3150 if (this->isEmpty()) {
3151 return isInverse;
3152 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003153
reed@google.com4db592c2013-10-30 17:39:43 +00003154 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003155 return isInverse;
3156 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003157
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003158 SkPath::Iter iter(*this, true);
3159 bool done = false;
3160 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003161 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003162 do {
3163 SkPoint pts[4];
3164 switch (iter.next(pts, false)) {
3165 case SkPath::kMove_Verb:
3166 case SkPath::kClose_Verb:
3167 break;
3168 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003169 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003170 break;
3171 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003172 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003173 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003174 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003175 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003176 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003177 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003178 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003179 break;
3180 case SkPath::kDone_Verb:
3181 done = true;
3182 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003183 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003184 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003185 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3186 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3187 if (evenOddFill) {
3188 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003189 }
caryclark9aacd902015-12-14 08:38:09 -08003190 if (w) {
3191 return !isInverse;
3192 }
3193 if (onCurveCount <= 1) {
3194 return SkToBool(onCurveCount) ^ isInverse;
3195 }
3196 if ((onCurveCount & 1) || evenOddFill) {
3197 return SkToBool(onCurveCount & 1) ^ isInverse;
3198 }
halcanary9d524f22016-03-29 09:03:52 -07003199 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003200 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3201 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003202 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003203 SkTDArray<SkVector> tangents;
3204 do {
3205 SkPoint pts[4];
3206 int oldCount = tangents.count();
3207 switch (iter.next(pts, false)) {
3208 case SkPath::kMove_Verb:
3209 case SkPath::kClose_Verb:
3210 break;
3211 case SkPath::kLine_Verb:
3212 tangent_line(pts, x, y, &tangents);
3213 break;
3214 case SkPath::kQuad_Verb:
3215 tangent_quad(pts, x, y, &tangents);
3216 break;
3217 case SkPath::kConic_Verb:
3218 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3219 break;
3220 case SkPath::kCubic_Verb:
3221 tangent_cubic(pts, x, y, &tangents);
3222 break;
3223 case SkPath::kDone_Verb:
3224 done = true;
3225 break;
3226 }
3227 if (tangents.count() > oldCount) {
3228 int last = tangents.count() - 1;
3229 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003230 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003231 tangents.remove(last);
3232 } else {
3233 for (int index = 0; index < last; ++index) {
3234 const SkVector& test = tangents[index];
3235 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003236 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3237 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003238 tangents.remove(last);
3239 tangents.removeShuffle(index);
3240 break;
3241 }
3242 }
3243 }
3244 }
3245 } while (!done);
3246 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003247}
fmalitaaa0df4e2015-12-01 09:13:23 -08003248
3249int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3250 SkScalar w, SkPoint pts[], int pow2) {
3251 const SkConic conic(p0, p1, p2, w);
3252 return conic.chopIntoQuadsPOW2(pts, pow2);
3253}
bsalomonedc743a2016-06-01 09:42:31 -07003254
3255bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3256 unsigned* start) {
3257 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3258 return false;
3259 }
3260 SkPath::RawIter iter(path);
3261 SkPoint verbPts[4];
3262 SkPath::Verb v;
3263 SkPoint rectPts[5];
3264 int rectPtCnt = 0;
3265 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3266 switch (v) {
3267 case SkPath::kMove_Verb:
3268 if (0 != rectPtCnt) {
3269 return false;
3270 }
3271 rectPts[0] = verbPts[0];
3272 ++rectPtCnt;
3273 break;
3274 case SkPath::kLine_Verb:
3275 if (5 == rectPtCnt) {
3276 return false;
3277 }
3278 rectPts[rectPtCnt] = verbPts[1];
3279 ++rectPtCnt;
3280 break;
3281 case SkPath::kClose_Verb:
3282 if (4 == rectPtCnt) {
3283 rectPts[4] = rectPts[0];
3284 rectPtCnt = 5;
3285 }
3286 break;
3287 default:
3288 return false;
3289 }
3290 }
3291 if (rectPtCnt < 5) {
3292 return false;
3293 }
3294 if (rectPts[0] != rectPts[4]) {
3295 return false;
3296 }
bsalomon057ae8a2016-07-24 05:37:26 -07003297 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3298 // and pts 1 and 2 the opposite vertical or horizontal edge).
3299 bool vec03IsVertical;
3300 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3301 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3302 // Make sure it has non-zero width and height
3303 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003304 return false;
3305 }
bsalomon057ae8a2016-07-24 05:37:26 -07003306 vec03IsVertical = true;
3307 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3308 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3309 // Make sure it has non-zero width and height
3310 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3311 return false;
3312 }
3313 vec03IsVertical = false;
3314 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003315 return false;
3316 }
bsalomon057ae8a2016-07-24 05:37:26 -07003317 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3318 // set if it is on the bottom edge.
3319 unsigned sortFlags =
3320 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3321 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3322 switch (sortFlags) {
3323 case 0b00:
3324 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3325 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3326 *start = 0;
3327 break;
3328 case 0b01:
3329 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3330 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3331 *start = 1;
3332 break;
3333 case 0b10:
3334 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3335 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3336 *start = 3;
3337 break;
3338 case 0b11:
3339 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3340 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3341 *start = 2;
3342 break;
bsalomonedc743a2016-06-01 09:42:31 -07003343 }
bsalomonedc743a2016-06-01 09:42:31 -07003344 return true;
3345}
bsalomon21af9ca2016-08-25 12:29:23 -07003346
3347void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3348 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3349 SkASSERT(!oval.isEmpty());
3350 SkASSERT(sweepAngle);
3351
3352 path->reset();
3353 path->setIsVolatile(true);
3354 path->setFillType(SkPath::kWinding_FillType);
3355 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3356 path->addOval(oval);
3357 return;
3358 }
3359 if (useCenter) {
3360 path->moveTo(oval.centerX(), oval.centerY());
3361 }
3362 // Arc to mods at 360 and drawArc is not supposed to.
3363 bool forceMoveTo = !useCenter;
3364 while (sweepAngle <= -360.f) {
3365 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3366 startAngle -= 180.f;
3367 path->arcTo(oval, startAngle, -180.f, false);
3368 startAngle -= 180.f;
3369 forceMoveTo = false;
3370 sweepAngle += 360.f;
3371 }
3372 while (sweepAngle >= 360.f) {
3373 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3374 startAngle += 180.f;
3375 path->arcTo(oval, startAngle, 180.f, false);
3376 startAngle += 180.f;
3377 forceMoveTo = false;
3378 sweepAngle -= 360.f;
3379 }
3380 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3381 if (useCenter) {
3382 path->close();
3383 }
3384}
Mike Reed0d7dac82017-02-02 17:45:56 -08003385
3386///////////////////////////////////////////////////////////////////////////////////////////////////
3387#include "SkNx.h"
3388
3389static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3390 SkScalar ts[2];
3391 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3392 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3393 SkASSERT(n >= 0 && n <= 2);
3394 for (int i = 0; i < n; ++i) {
3395 extremas[i] = SkEvalQuadAt(src, ts[i]);
3396 }
3397 extremas[n] = src[2];
3398 return n + 1;
3399}
3400
3401static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3402 SkConic conic(src[0], src[1], src[2], w);
3403 SkScalar ts[2];
3404 int n = conic.findXExtrema(ts);
3405 n += conic.findYExtrema(&ts[n]);
3406 SkASSERT(n >= 0 && n <= 2);
3407 for (int i = 0; i < n; ++i) {
3408 extremas[i] = conic.evalAt(ts[i]);
3409 }
3410 extremas[n] = src[2];
3411 return n + 1;
3412}
3413
3414static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3415 SkScalar ts[4];
3416 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3417 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3418 SkASSERT(n >= 0 && n <= 4);
3419 for (int i = 0; i < n; ++i) {
3420 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3421 }
3422 extremas[n] = src[3];
3423 return n + 1;
3424}
3425
Mike Reed8d3196b2017-02-03 11:34:13 -05003426SkRect SkPath::computeTightBounds() const {
3427 if (0 == this->countVerbs()) {
3428 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003429 }
3430
Mike Reed8d3196b2017-02-03 11:34:13 -05003431 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3432 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003433 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003434
Mike Reed0d7dac82017-02-02 17:45:56 -08003435 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3436 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003437 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003438
3439 // initial with the first MoveTo, so we don't have to check inside the switch
3440 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003441 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003442 for (;;) {
3443 int count = 0;
3444 switch (iter.next(pts)) {
3445 case SkPath::kMove_Verb:
3446 extremas[0] = pts[0];
3447 count = 1;
3448 break;
3449 case SkPath::kLine_Verb:
3450 extremas[0] = pts[1];
3451 count = 1;
3452 break;
3453 case SkPath::kQuad_Verb:
3454 count = compute_quad_extremas(pts, extremas);
3455 break;
3456 case SkPath::kConic_Verb:
3457 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3458 break;
3459 case SkPath::kCubic_Verb:
3460 count = compute_cubic_extremas(pts, extremas);
3461 break;
3462 case SkPath::kClose_Verb:
3463 break;
3464 case SkPath::kDone_Verb:
3465 goto DONE;
3466 }
3467 for (int i = 0; i < count; ++i) {
3468 Sk2s tmp = from_point(extremas[i]);
3469 min = Sk2s::Min(min, tmp);
3470 max = Sk2s::Max(max, tmp);
3471 }
3472 }
3473DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003474 SkRect bounds;
3475 min.store((SkPoint*)&bounds.fLeft);
3476 max.store((SkPoint*)&bounds.fRight);
3477 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003478}
Cary Clarkdf429f32017-11-08 11:44:31 -05003479
3480bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3481 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3482}
3483
3484bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3485 const SkPoint& p3, bool exact) {
3486 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3487 SkPointPriv::EqualsWithinTolerance(p2, p3);
3488}
3489
3490bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3491 const SkPoint& p3, const SkPoint& p4, bool exact) {
3492 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3493 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3494 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3495 SkPointPriv::EqualsWithinTolerance(p3, p4);
3496}