blob: 0683c174f126fdb5865faf6e77fa9badb23826d2 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050019#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020
Mike Reeda99b6ce2017-02-04 11:04:26 -050021static float poly_eval(float A, float B, float C, float t) {
22 return (A * t + B) * t + C;
23}
24
25static float poly_eval(float A, float B, float C, float D, float t) {
26 return ((A * t + B) * t + C) * t + D;
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029////////////////////////////////////////////////////////////////////////////
30
reed@google.com3563c9e2011-11-14 19:34:57 +000031/**
32 * Path.bounds is defined to be the bounds of all the control points.
33 * If we called bounds.join(r) we would skip r if r was empty, which breaks
34 * our promise. Hence we have a custom joiner that doesn't look at emptiness
35 */
36static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
37 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
38 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
39 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
40 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
41}
42
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043static bool is_degenerate(const SkPath& path) {
44 SkPath::Iter iter(path, false);
45 SkPoint pts[4];
46 return SkPath::kDone_Verb == iter.next(pts);
47}
48
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049class SkAutoDisableDirectionCheck {
50public:
51 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070052 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070056 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057 }
58
59private:
reed026beb52015-06-10 14:23:15 -070060 SkPath* fPath;
61 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000063#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065/* This guy's constructor/destructor bracket a path editing operation. It is
66 used when we know the bounds of the amount we are going to add to the path
67 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000070 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000071 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000072
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000073 It also notes if the path was originally degenerate, and if so, sets
74 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000075 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 */
77class SkAutoPathBoundsUpdate {
78public:
79 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
80 this->init(path);
81 }
82
83 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
84 SkScalar right, SkScalar bottom) {
85 fRect.set(left, top, right, bottom);
86 this->init(path);
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000090 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
91 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050092 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 }
95 }
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 SkPath* fPath;
99 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 // Cannot use fRect for our bounds unless we know it is sorted
106 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000108 // Mark the path's bounds as dirty if (1) they are, or (2) the path
109 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 if (fHasValidBounds && !fEmpty) {
113 joinNoEmptyChecks(&fRect, fPath->getBounds());
114 }
115 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 }
117};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000118#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000139SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800140 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700142 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400143 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144}
145
146void SkPath::resetFields() {
147 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000148 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000150 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700151 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000152
153 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700154 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 this->copyFields(that);
160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 }
174 SkDEBUGCODE(this->validate();)
175 return *this;
176}
177
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178void SkPath::copyFields(const SkPath& that) {
179 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000180 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400183 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400184
185 // Non-atomic assignment of atomic values.
186 fConvexity .store(that.fConvexity .load());
187 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000193 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000194 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700199 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000200 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400203
204 // Non-atomic swaps of atomic values.
205 Convexity c = fConvexity.load();
206 fConvexity.store(that.fConvexity.load());
207 that.fConvexity.store(c);
208
209 uint8_t fd = fFirstDirection.load();
210 fFirstDirection.store(that.fFirstDirection.load());
211 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
caryclark8e7b19d2016-02-18 04:11:48 -0800215bool SkPath::isInterpolatable(const SkPath& compare) const {
216 int count = fPathRef->countVerbs();
217 if (count != compare.fPathRef->countVerbs()) {
218 return false;
219 }
220 if (!count) {
221 return true;
222 }
223 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
224 count)) {
225 return false;
226 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800227 return !fPathRef->countWeights() ||
228 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800229 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
230}
231
232bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500233 int pointCount = fPathRef->countPoints();
234 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800235 return false;
236 }
Cary Clark7487ec82018-03-06 09:30:46 -0500237 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800238 return true;
239 }
caryclark8e7b19d2016-02-18 04:11:48 -0800240 out->reset();
241 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700242 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return true;
244}
245
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246static inline bool check_edge_against_rect(const SkPoint& p0,
247 const SkPoint& p1,
248 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700249 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 const SkPoint* edgeBegin;
251 SkVector v;
reed026beb52015-06-10 14:23:15 -0700252 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 v = p1 - p0;
254 edgeBegin = &p0;
255 } else {
256 v = p0 - p1;
257 edgeBegin = &p1;
258 }
259 if (v.fX || v.fY) {
260 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500261 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
262 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
263 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
264 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
266 return false;
267 }
268 }
269 return true;
270}
271
272bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
273 // This only handles non-degenerate convex paths currently.
274 if (kConvex_Convexity != this->getConvexity()) {
275 return false;
276 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000277
reed026beb52015-06-10 14:23:15 -0700278 SkPathPriv::FirstDirection direction;
279 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 return false;
281 }
282
283 SkPoint firstPt;
284 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500285 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkPath::Verb verb;
287 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400288 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 int nextPt = -1;
294 switch (verb) {
295 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(++moveCnt);
298 firstPt = prevPt = pts[0];
299 break;
300 case kLine_Verb:
301 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 break;
305 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000306 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 2;
310 break;
311 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 3;
315 break;
316 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 break;
319 default:
320 SkDEBUGFAIL("unknown verb");
321 }
322 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800323 if (SkPath::kConic_Verb == verb) {
324 SkConic orig;
325 orig.set(pts, iter.conicWeight());
326 SkPoint quadPts[5];
327 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800328 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800329
330 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
331 return false;
332 }
333 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
334 return false;
335 }
336 } else {
337 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
338 return false;
339 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400345 if (segmentCount) {
346 return check_edge_against_rect(prevPt, firstPt, rect, direction);
347 }
348 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000349}
350
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000351uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800353#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400354 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
355 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000356#endif
357 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000358}
djsollen@google.come63793a2012-03-21 15:39:03 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360void SkPath::reset() {
361 SkDEBUGCODE(this->validate();)
362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000364 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
367void SkPath::rewind() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
fsb1475b02016-01-20 09:51:07 -0800374bool SkPath::isLastContourClosed() const {
375 int verbCount = fPathRef->countVerbs();
376 if (0 == verbCount) {
377 return false;
378 }
379 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
380}
381
reed@google.com7e6c4d12012-05-10 14:05:43 +0000382bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000385 if (2 == verbCount) {
386 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
387 if (kLine_Verb == fPathRef->atVerb(1)) {
388 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391 line[0] = pts[0];
392 line[1] = pts[1];
393 }
394 return true;
395 }
396 }
397 return false;
398}
399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400/*
401 Determines if path is a rect by keeping track of changes in direction
402 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 The direction is computed such that:
405 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000408 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410A rectangle cycles up/right/down/left or up/left/down/right.
411
412The test fails if:
413 The path is closed, and followed by a line.
414 A second move creates a new endpoint.
415 A diagonal line is parsed.
416 There's more than four changes of direction.
417 There's a discontinuity on the line (e.g., a move in the middle)
418 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 *The rectangle doesn't complete a cycle.
422 *The final point isn't equal to the first point.
423
424 *These last two conditions we relax if we have a 3-edge path that would
425 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427It's OK if the path has:
428 Several colinear line segments composing a rectangle side.
429 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431The direction takes advantage of the corners found since opposite sides
432must travel in opposite directions.
433
434FIXME: Allow colinear quads and cubics to be treated like lines.
435FIXME: If the API passes fill-only, return true if the filled stroke
436 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000437
Cary Clark48c464a2018-04-16 12:06:07 -0400438 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000439 0x1 is set if the segment is horizontal
440 0x2 is set if the segment is moving to the right or down
441 thus:
442 two directions are opposites iff (dirA ^ dirB) == 0x2
443 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000444
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446static int rect_make_dir(SkScalar dx, SkScalar dy) {
447 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
448}
Cary Clark88ba9712018-04-12 14:00:24 -0400449
caryclark@google.comf68154a2012-11-21 15:18:06 +0000450bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400451 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400453 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400454 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400455 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
456 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400457 SkPoint firstCorner;
458 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000459 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400460 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400461 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400462 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000463 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700465 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000466 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000467 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700468 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
469 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700473 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400475 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400476 lastPt = pts;
477 }
Cary Clark88ba9712018-04-12 14:00:24 -0400478 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
479 SkVector lineDelta = lineEnd - lineStart;
480 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 return false; // diagonal
482 }
Cary Clark88ba9712018-04-12 14:00:24 -0400483 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000484 break; // single point on side OK
485 }
Cary Clark48c464a2018-04-16 12:06:07 -0400486 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400488 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 corners = 1;
490 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400491 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 break;
493 }
494 if (closedOrMoved) {
495 return false; // closed followed by a line
496 }
Cary Clark48c464a2018-04-16 12:06:07 -0400497 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000498 break; // colinear with first
499 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000500 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400501 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400502 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400503 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400504 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400505 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 break; // colinear segment
507 }
Cary Clark48c464a2018-04-16 12:06:07 -0400508 directions[corners++] = nextDirection;
509 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400510 switch (corners) {
511 case 2:
512 firstCorner = lineStart;
513 break;
514 case 3:
515 if ((directions[0] ^ directions[2]) != 2) {
516 return false;
517 }
518 thirdCorner = lineEnd;
519 break;
520 case 4:
521 if ((directions[1] ^ directions[3]) != 2) {
522 return false;
523 }
524 break;
525 default:
526 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000527 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400528 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000529 break;
530 }
531 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000532 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000533 case kCubic_Verb:
534 return false; // quadratic, cubic not allowed
535 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400536 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700537 insertClose = true;
538 *currVerb -= 1; // try move again afterwards
539 goto addMissingClose;
540 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400541 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400542 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400543 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400544 closeXY = *firstPt - *lastPt;
545 if (closeXY.fX && closeXY.fY) {
546 return false; // we're diagonal, abort
547 }
Cary Clark8540e112018-04-11 14:30:27 -0400548 }
Cary Clark88ba9712018-04-12 14:00:24 -0400549 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000550 closedOrMoved = true;
551 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000552 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000553 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000554 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000555 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700557addMissingClose:
558 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000559 }
560 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400561 if (corners < 3 || corners > 4) {
562 return false;
563 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000564 if (savePts) {
565 *ptsPtr = savePts;
566 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400567 // check if close generates diagonal
568 closeXY = *firstPt - *lastPt;
569 if (closeXY.fX && closeXY.fY) {
570 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400571 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400572 if (rect) {
573 rect->set(firstCorner, thirdCorner);
574 }
575 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000576 *isClosed = autoClose;
577 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400578 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400579 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000580 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400581 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000582}
583
robertphillips4f662e62014-12-29 14:06:51 -0800584bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000585 SkDEBUGCODE(this->validate();)
586 int currVerb = 0;
587 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400588 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000589}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000590
caryclark95bc5f32015-04-08 08:34:15 -0700591bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000592 SkDEBUGCODE(this->validate();)
593 int currVerb = 0;
594 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000595 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400596 SkRect testRects[2];
597 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000598 return false;
599 }
Cary Clark5c715182018-04-09 16:07:11 -0400600 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000601 if (testRects[0].contains(testRects[1])) {
602 if (rects) {
603 rects[0] = testRects[0];
604 rects[1] = testRects[1];
605 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000606 if (dirs) {
607 dirs[0] = testDirs[0];
608 dirs[1] = testDirs[1];
609 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 return true;
611 }
612 if (testRects[1].contains(testRects[0])) {
613 if (rects) {
614 rects[0] = testRects[1];
615 rects[1] = testRects[0];
616 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000617 if (dirs) {
618 dirs[0] = testDirs[1];
619 dirs[1] = testDirs[0];
620 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000621 return true;
622 }
623 }
624 return false;
625}
626
Mike Reed0c3137c2018-02-20 13:57:05 -0500627bool SkPath::isOval(SkRect* bounds) const {
628 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
629}
630
631bool SkPath::isRRect(SkRRect* rrect) const {
632 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
633}
634
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635int SkPath::countPoints() const {
636 return fPathRef->countPoints();
637}
638
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000639int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000640 SkDEBUGCODE(this->validate();)
641
642 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000643 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000644 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800645 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000646 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647}
648
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000649SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000650 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
651 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000652 }
653 return SkPoint::Make(0, 0);
654}
655
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656int SkPath::countVerbs() const {
657 return fPathRef->countVerbs();
658}
659
660static inline void copy_verbs_reverse(uint8_t* inorderDst,
661 const uint8_t* reversedSrc,
662 int count) {
663 for (int i = 0; i < count; ++i) {
664 inorderDst[i] = reversedSrc[~i];
665 }
666}
667
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000668int SkPath::getVerbs(uint8_t dst[], int max) const {
669 SkDEBUGCODE(this->validate();)
670
671 SkASSERT(max >= 0);
672 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000673 int count = SkMin32(max, fPathRef->countVerbs());
674 copy_verbs_reverse(dst, fPathRef->verbs(), count);
675 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000676}
677
reed@google.com294dd7b2011-10-11 11:58:32 +0000678bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000679 SkDEBUGCODE(this->validate();)
680
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000682 if (count > 0) {
683 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000686 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000688 if (lastPt) {
689 lastPt->set(0, 0);
690 }
691 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692}
693
caryclarkaec25102015-04-29 08:28:30 -0700694void SkPath::setPt(int index, SkScalar x, SkScalar y) {
695 SkDEBUGCODE(this->validate();)
696
697 int count = fPathRef->countPoints();
698 if (count <= index) {
699 return;
700 } else {
701 SkPathRef::Editor ed(&fPathRef);
702 ed.atPoint(index)->set(x, y);
703 }
704}
705
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706void SkPath::setLastPt(SkScalar x, SkScalar y) {
707 SkDEBUGCODE(this->validate();)
708
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000709 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710 if (count == 0) {
711 this->moveTo(x, y);
712 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000713 SkPathRef::Editor ed(&fPathRef);
714 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000715 }
716}
717
reed@google.com04863fa2011-05-15 04:08:24 +0000718void SkPath::setConvexity(Convexity c) {
719 if (fConvexity != c) {
720 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000721 }
722}
723
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724//////////////////////////////////////////////////////////////////////////////
725// Construction methods
726
reed026beb52015-06-10 14:23:15 -0700727#define DIRTY_AFTER_EDIT \
728 do { \
729 fConvexity = kUnknown_Convexity; \
730 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000731 } while (0)
732
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733void SkPath::incReserve(U16CPU inc) {
734 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000735 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736 SkDEBUGCODE(this->validate();)
737}
738
739void SkPath::moveTo(SkScalar x, SkScalar y) {
740 SkDEBUGCODE(this->validate();)
741
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000742 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000744 // remember our index
745 fLastMoveToIndex = fPathRef->countPoints();
746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700748
749 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750}
751
752void SkPath::rMoveTo(SkScalar x, SkScalar y) {
753 SkPoint pt;
754 this->getLastPt(&pt);
755 this->moveTo(pt.fX + x, pt.fY + y);
756}
757
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000758void SkPath::injectMoveToIfNeeded() {
759 if (fLastMoveToIndex < 0) {
760 SkScalar x, y;
761 if (fPathRef->countVerbs() == 0) {
762 x = y = 0;
763 } else {
764 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
765 x = pt.fX;
766 y = pt.fY;
767 }
768 this->moveTo(x, y);
769 }
770}
771
reed@android.com8a1c16f2008-12-17 15:59:43 +0000772void SkPath::lineTo(SkScalar x, SkScalar y) {
773 SkDEBUGCODE(this->validate();)
774
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000775 this->injectMoveToIfNeeded();
776
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000777 SkPathRef::Editor ed(&fPathRef);
778 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779
reed@google.comb54455e2011-05-16 14:16:04 +0000780 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000781}
782
783void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000784 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785 SkPoint pt;
786 this->getLastPt(&pt);
787 this->lineTo(pt.fX + x, pt.fY + y);
788}
789
790void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
791 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000792
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000793 this->injectMoveToIfNeeded();
794
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000795 SkPathRef::Editor ed(&fPathRef);
796 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 pts[0].set(x1, y1);
798 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000799
reed@google.comb54455e2011-05-16 14:16:04 +0000800 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801}
802
803void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000804 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000805 SkPoint pt;
806 this->getLastPt(&pt);
807 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
808}
809
reed@google.com277c3f82013-05-31 15:17:50 +0000810void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
811 SkScalar w) {
812 // check for <= 0 or NaN with this test
813 if (!(w > 0)) {
814 this->lineTo(x2, y2);
815 } else if (!SkScalarIsFinite(w)) {
816 this->lineTo(x1, y1);
817 this->lineTo(x2, y2);
818 } else if (SK_Scalar1 == w) {
819 this->quadTo(x1, y1, x2, y2);
820 } else {
821 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000822
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000823 this->injectMoveToIfNeeded();
824
reed@google.com277c3f82013-05-31 15:17:50 +0000825 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000826 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000827 pts[0].set(x1, y1);
828 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000829
reed@google.com277c3f82013-05-31 15:17:50 +0000830 DIRTY_AFTER_EDIT;
831 }
832}
833
834void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
835 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000836 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000837 SkPoint pt;
838 this->getLastPt(&pt);
839 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
840}
841
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
843 SkScalar x3, SkScalar y3) {
844 SkDEBUGCODE(this->validate();)
845
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000846 this->injectMoveToIfNeeded();
847
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 SkPathRef::Editor ed(&fPathRef);
849 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000850 pts[0].set(x1, y1);
851 pts[1].set(x2, y2);
852 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853
reed@google.comb54455e2011-05-16 14:16:04 +0000854 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855}
856
857void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
858 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000859 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 SkPoint pt;
861 this->getLastPt(&pt);
862 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
863 pt.fX + x3, pt.fY + y3);
864}
865
866void SkPath::close() {
867 SkDEBUGCODE(this->validate();)
868
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000869 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000870 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000871 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872 case kLine_Verb:
873 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000874 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000876 case kMove_Verb: {
877 SkPathRef::Editor ed(&fPathRef);
878 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000880 }
reed@google.com277c3f82013-05-31 15:17:50 +0000881 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000882 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000883 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000884 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000885 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000886 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000887 }
888 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000889
890 // signal that we need a moveTo to follow us (unless we're done)
891#if 0
892 if (fLastMoveToIndex >= 0) {
893 fLastMoveToIndex = ~fLastMoveToIndex;
894 }
895#else
896 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
897#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000898}
899
900///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000901
fmalitac08d53e2015-11-17 09:53:29 -0800902namespace {
903
904template <unsigned N>
905class PointIterator {
906public:
907 PointIterator(SkPath::Direction dir, unsigned startIndex)
908 : fCurrent(startIndex % N)
909 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
910
911 const SkPoint& current() const {
912 SkASSERT(fCurrent < N);
913 return fPts[fCurrent];
914 }
915
916 const SkPoint& next() {
917 fCurrent = (fCurrent + fAdvance) % N;
918 return this->current();
919 }
920
921protected:
922 SkPoint fPts[N];
923
924private:
925 unsigned fCurrent;
926 unsigned fAdvance;
927};
928
929class RectPointIterator : public PointIterator<4> {
930public:
931 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
932 : PointIterator(dir, startIndex) {
933
934 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
935 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
936 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
937 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
938 }
939};
940
941class OvalPointIterator : public PointIterator<4> {
942public:
943 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
944 : PointIterator(dir, startIndex) {
945
946 const SkScalar cx = oval.centerX();
947 const SkScalar cy = oval.centerY();
948
949 fPts[0] = SkPoint::Make(cx, oval.fTop);
950 fPts[1] = SkPoint::Make(oval.fRight, cy);
951 fPts[2] = SkPoint::Make(cx, oval.fBottom);
952 fPts[3] = SkPoint::Make(oval.fLeft, cy);
953 }
954};
955
956class RRectPointIterator : public PointIterator<8> {
957public:
958 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
959 : PointIterator(dir, startIndex) {
960
961 const SkRect& bounds = rrect.getBounds();
962 const SkScalar L = bounds.fLeft;
963 const SkScalar T = bounds.fTop;
964 const SkScalar R = bounds.fRight;
965 const SkScalar B = bounds.fBottom;
966
967 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
968 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
969 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
970 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
971 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
972 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
973 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
974 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
975 }
976};
977
978} // anonymous namespace
979
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000980static void assert_known_direction(int dir) {
981 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
982}
983
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800985 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986}
987
988void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
989 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800990 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
991}
992
993void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000994 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700995 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800996 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000997 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -0800998 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000999
fmalitac08d53e2015-11-17 09:53:29 -08001000 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001001
fmalitac08d53e2015-11-17 09:53:29 -08001002 const int kVerbs = 5; // moveTo + 3x lineTo + close
1003 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004
fmalitac08d53e2015-11-17 09:53:29 -08001005 RectPointIterator iter(rect, dir, startIndex);
1006
1007 this->moveTo(iter.current());
1008 this->lineTo(iter.next());
1009 this->lineTo(iter.next());
1010 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001012
1013 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014}
1015
reed@google.com744faba2012-05-29 19:54:52 +00001016void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1017 SkDEBUGCODE(this->validate();)
1018 if (count <= 0) {
1019 return;
1020 }
1021
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001022 fLastMoveToIndex = fPathRef->countPoints();
1023
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001024 // +close makes room for the extra kClose_Verb
1025 SkPathRef::Editor ed(&fPathRef, count+close, count);
1026
1027 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001028 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001029 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1030 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001031 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032
reed@google.com744faba2012-05-29 19:54:52 +00001033 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001034 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001035 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001036 }
1037
reed@google.com744faba2012-05-29 19:54:52 +00001038 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001039 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001040}
1041
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001042#include "SkGeometry.h"
1043
reedf90ea012015-01-29 12:03:58 -08001044static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1045 SkPoint* pt) {
1046 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001047 // Chrome uses this path to move into and out of ovals. If not
1048 // treated as a special case the moves can distort the oval's
1049 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001050 pt->set(oval.fRight, oval.centerY());
1051 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001052 } else if (0 == oval.width() && 0 == oval.height()) {
1053 // Chrome will sometimes create 0 radius round rects. Having degenerate
1054 // quad segments in the path prevents the path from being recognized as
1055 // a rect.
1056 // TODO: optimizing the case where only one of width or height is zero
1057 // should also be considered. This case, however, doesn't seem to be
1058 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001059 pt->set(oval.fRight, oval.fTop);
1060 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001061 }
reedf90ea012015-01-29 12:03:58 -08001062 return false;
1063}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064
reedd5d27d92015-02-09 13:54:43 -08001065// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1066//
1067static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1068 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1069 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1070 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071
1072 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001073 loss in radians-conversion and/or sin/cos, we may end up with coincident
1074 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1075 of drawing a nearly complete circle (good).
1076 e.g. canvas.drawArc(0, 359.99, ...)
1077 -vs- canvas.drawArc(0, 359.9, ...)
1078 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079 */
reedd5d27d92015-02-09 13:54:43 -08001080 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001081 SkScalar sw = SkScalarAbs(sweepAngle);
1082 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1083 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1084 // make a guess at a tiny angle (in radians) to tweak by
1085 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1086 // not sure how much will be enough, so we use a loop
1087 do {
1088 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001089 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1090 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001091 }
1092 }
reedd5d27d92015-02-09 13:54:43 -08001093 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1094}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001095
reed9e779d42015-02-17 11:43:14 -08001096/**
1097 * If this returns 0, then the caller should just line-to the singlePt, else it should
1098 * ignore singlePt and append the specified number of conics.
1099 */
reedd5d27d92015-02-09 13:54:43 -08001100static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001101 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1102 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001103 SkMatrix matrix;
1104
1105 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1106 matrix.postTranslate(oval.centerX(), oval.centerY());
1107
reed9e779d42015-02-17 11:43:14 -08001108 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1109 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001110 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001111 }
1112 return count;
reedd5d27d92015-02-09 13:54:43 -08001113}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001114
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001115void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001116 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001117 SkRRect rrect;
1118 rrect.setRectRadii(rect, (const SkVector*) radii);
1119 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120}
1121
reed@google.com4ed0fb72012-12-12 20:48:18 +00001122void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001123 // legacy start indices: 6 (CW) and 7(CCW)
1124 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1125}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001126
fmalitac08d53e2015-11-17 09:53:29 -08001127void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1128 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001129
caryclarkda707bf2015-11-19 14:47:43 -08001130 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001131 const SkRect& bounds = rrect.getBounds();
1132
Brian Salomon0a241ce2017-12-15 11:31:05 -05001133 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001134 // degenerate(rect) => radii points are collapsing
1135 this->addRect(bounds, dir, (startIndex + 1) / 2);
1136 } else if (rrect.isOval()) {
1137 // degenerate(oval) => line points are collapsing
1138 this->addOval(bounds, dir, startIndex / 2);
1139 } else {
1140 fFirstDirection = this->hasOnlyMoveTos() ?
1141 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1142
1143 SkAutoPathBoundsUpdate apbu(this, bounds);
1144 SkAutoDisableDirectionCheck addc(this);
1145
1146 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1147 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1148 const SkScalar weight = SK_ScalarRoot2Over2;
1149
1150 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1151 const int kVerbs = startsWithConic
1152 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1153 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1154 this->incReserve(kVerbs);
1155
1156 RRectPointIterator rrectIter(rrect, dir, startIndex);
1157 // Corner iterator indices follow the collapsed radii model,
1158 // adjusted such that the start pt is "behind" the radii start pt.
1159 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1160 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1161
1162 this->moveTo(rrectIter.current());
1163 if (startsWithConic) {
1164 for (unsigned i = 0; i < 3; ++i) {
1165 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1166 this->lineTo(rrectIter.next());
1167 }
1168 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1169 // final lineTo handled by close().
1170 } else {
1171 for (unsigned i = 0; i < 4; ++i) {
1172 this->lineTo(rrectIter.next());
1173 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1174 }
1175 }
1176 this->close();
1177
caryclarkda707bf2015-11-19 14:47:43 -08001178 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001179 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001180
fmalitac08d53e2015-11-17 09:53:29 -08001181 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1182 }
1183
1184 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001185}
1186
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001187bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001188 int count = fPathRef->countVerbs();
1189 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1190 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191 if (*verbs == kLine_Verb ||
1192 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001193 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194 *verbs == kCubic_Verb) {
1195 return false;
1196 }
1197 ++verbs;
1198 }
1199 return true;
1200}
1201
Brian Osmana2318572017-07-10 15:09:26 -04001202bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1203 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001204 if (count < 2) {
1205 return true;
1206 }
Brian Osmana2318572017-07-10 15:09:26 -04001207 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001208 const SkPoint& first = *pts;
1209 for (int index = 1; index < count; ++index) {
1210 if (first != pts[index]) {
1211 return false;
1212 }
1213 }
1214 return true;
1215}
1216
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001217void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1218 Direction dir) {
1219 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001220
humper@google.com75e3ca12013-04-08 21:44:11 +00001221 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001222 return;
1223 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001224
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001225 SkRRect rrect;
1226 rrect.setRectXY(rect, rx, ry);
1227 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001228}
1229
reed@android.com8a1c16f2008-12-17 15:59:43 +00001230void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001231 // legacy start index: 1
1232 this->addOval(oval, dir, 1);
1233}
1234
1235void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001236 assert_known_direction(dir);
1237
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001238 /* If addOval() is called after previous moveTo(),
1239 this path is still marked as an oval. This is used to
1240 fit into WebKit's calling sequences.
1241 We can't simply check isEmpty() in this case, as additional
1242 moveTo() would mark the path non empty.
1243 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001244 bool isOval = hasOnlyMoveTos();
1245 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001246 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001247 } else {
reed026beb52015-06-10 14:23:15 -07001248 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001249 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001250
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001251 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001252 SkAutoPathBoundsUpdate apbu(this, oval);
1253
fmalitac08d53e2015-11-17 09:53:29 -08001254 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1255 const int kVerbs = 6; // moveTo + 4x conicTo + close
1256 this->incReserve(kVerbs);
1257
1258 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1259 // The corner iterator pts are tracking "behind" the oval/radii pts.
1260 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001261 const SkScalar weight = SK_ScalarRoot2Over2;
1262
fmalitac08d53e2015-11-17 09:53:29 -08001263 this->moveTo(ovalIter.current());
1264 for (unsigned i = 0; i < 4; ++i) {
1265 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001266 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001267 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268
fmalitac08d53e2015-11-17 09:53:29 -08001269 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1270
robertphillips@google.com466310d2013-12-03 16:43:54 +00001271 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001272
bsalomon78d58d12016-05-27 09:17:04 -07001273 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001274}
1275
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1277 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001278 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 }
1280}
1281
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1283 bool forceMoveTo) {
1284 if (oval.width() < 0 || oval.height() < 0) {
1285 return;
1286 }
1287
reedf90ea012015-01-29 12:03:58 -08001288 if (fPathRef->countVerbs() == 0) {
1289 forceMoveTo = true;
1290 }
1291
1292 SkPoint lonePt;
1293 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1294 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1295 return;
1296 }
1297
reedd5d27d92015-02-09 13:54:43 -08001298 SkVector startV, stopV;
1299 SkRotationDirection dir;
1300 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1301
reed9e779d42015-02-17 11:43:14 -08001302 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001303
1304 // At this point, we know that the arc is not a lone point, but startV == stopV
1305 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1306 // cannot handle it.
1307 if (startV == stopV) {
1308 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1309 SkScalar radiusX = oval.width() / 2;
1310 SkScalar radiusY = oval.height() / 2;
1311 // We cannot use SkScalarSinCos function in the next line because
1312 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1313 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001314 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001315 // make sin(endAngle) to be 0 which will then draw a dot.
1316 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1317 oval.centerY() + radiusY * sk_float_sin(endAngle));
1318 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1319 return;
1320 }
1321
reedd5d27d92015-02-09 13:54:43 -08001322 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001323 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001324 if (count) {
1325 this->incReserve(count * 2 + 1);
1326 const SkPoint& pt = conics[0].fPts[0];
1327 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1328 for (int i = 0; i < count; ++i) {
1329 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1330 }
reed9e779d42015-02-17 11:43:14 -08001331 } else {
1332 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001333 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334}
1335
caryclark55d49052016-01-23 05:07:04 -08001336// This converts the SVG arc to conics.
1337// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1338// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1339// See also SVG implementation notes:
1340// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1341// Note that arcSweep bool value is flipped from the original implementation.
1342void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1343 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001344 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001345 SkPoint srcPts[2];
1346 this->getLastPt(&srcPts[0]);
1347 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1348 // joining the endpoints.
1349 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1350 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001351 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001352 return;
1353 }
1354 // If the current point and target point for the arc are identical, it should be treated as a
1355 // zero length path. This ensures continuity in animations.
1356 srcPts[1].set(x, y);
1357 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001358 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001359 return;
1360 }
1361 rx = SkScalarAbs(rx);
1362 ry = SkScalarAbs(ry);
1363 SkVector midPointDistance = srcPts[0] - srcPts[1];
1364 midPointDistance *= 0.5f;
1365
1366 SkMatrix pointTransform;
1367 pointTransform.setRotate(-angle);
1368
1369 SkPoint transformedMidPoint;
1370 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1371 SkScalar squareRx = rx * rx;
1372 SkScalar squareRy = ry * ry;
1373 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1374 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1375
1376 // Check if the radii are big enough to draw the arc, scale radii if not.
1377 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1378 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1379 if (radiiScale > 1) {
1380 radiiScale = SkScalarSqrt(radiiScale);
1381 rx *= radiiScale;
1382 ry *= radiiScale;
1383 }
1384
1385 pointTransform.setScale(1 / rx, 1 / ry);
1386 pointTransform.preRotate(-angle);
1387
1388 SkPoint unitPts[2];
1389 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1390 SkVector delta = unitPts[1] - unitPts[0];
1391
1392 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1393 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1394
1395 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1396 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1397 scaleFactor = -scaleFactor;
1398 }
1399 delta.scale(scaleFactor);
1400 SkPoint centerPoint = unitPts[0] + unitPts[1];
1401 centerPoint *= 0.5f;
1402 centerPoint.offset(-delta.fY, delta.fX);
1403 unitPts[0] -= centerPoint;
1404 unitPts[1] -= centerPoint;
1405 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1406 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1407 SkScalar thetaArc = theta2 - theta1;
1408 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1409 thetaArc += SK_ScalarPI * 2;
1410 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1411 thetaArc -= SK_ScalarPI * 2;
1412 }
1413 pointTransform.setRotate(angle);
1414 pointTransform.preScale(rx, ry);
1415
Cary Clark1acd3c72017-12-08 11:37:01 -05001416#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001417 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001418#else
1419 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1420 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1421#endif
caryclark55d49052016-01-23 05:07:04 -08001422 SkScalar thetaWidth = thetaArc / segments;
1423 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1424 if (!SkScalarIsFinite(t)) {
1425 return;
1426 }
1427 SkScalar startTheta = theta1;
1428 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001429#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1430 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1431 return scalar == SkScalarFloorToScalar(scalar);
1432 };
1433 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1434 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1435 scalar_is_integer(x) && scalar_is_integer(y);
1436#endif
caryclark55d49052016-01-23 05:07:04 -08001437 for (int i = 0; i < segments; ++i) {
1438 SkScalar endTheta = startTheta + thetaWidth;
1439 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1440
1441 unitPts[1].set(cosEndTheta, sinEndTheta);
1442 unitPts[1] += centerPoint;
1443 unitPts[0] = unitPts[1];
1444 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1445 SkPoint mapped[2];
1446 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001447 /*
1448 Computing the arc width introduces rounding errors that cause arcs to start
1449 outside their marks. A round rect may lose convexity as a result. If the input
1450 values are on integers, place the conic on integers as well.
1451 */
1452#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1453 if (expectIntegers) {
1454 SkScalar* mappedScalars = &mapped[0].fX;
1455 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1456 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1457 }
1458 }
1459#endif
caryclark55d49052016-01-23 05:07:04 -08001460 this->conicTo(mapped[0], mapped[1], w);
1461 startTheta = endTheta;
1462 }
1463}
1464
1465void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1466 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1467 SkPoint currentPoint;
1468 this->getLastPt(&currentPoint);
1469 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1470}
1471
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001472void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001473 if (oval.isEmpty() || 0 == sweepAngle) {
1474 return;
1475 }
1476
1477 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1478
1479 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001480 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1481 // See SkPath::addOval() docs.
1482 SkScalar startOver90 = startAngle / 90.f;
1483 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1484 SkScalar error = startOver90 - startOver90I;
1485 if (SkScalarNearlyEqual(error, 0)) {
1486 // Index 1 is at startAngle == 0.
1487 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1488 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1489 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1490 (unsigned) startIndex);
1491 return;
1492 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001493 }
bsalomon1978ce22016-05-31 10:42:16 -07001494 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495}
1496
1497/*
1498 Need to handle the case when the angle is sharp, and our computed end-points
1499 for the arc go behind pt1 and/or p2...
1500*/
reedc7789042015-01-29 12:59:11 -08001501void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001502 if (radius == 0) {
1503 this->lineTo(x1, y1);
1504 return;
1505 }
1506
1507 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001508
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 // need to know our prev pt so we can construct tangent vectors
1510 {
1511 SkPoint start;
1512 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001513 // Handle degenerate cases by adding a line to the first point and
1514 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 before.setNormalize(x1 - start.fX, y1 - start.fY);
1516 after.setNormalize(x2 - x1, y2 - y1);
1517 }
reed@google.comabf15c12011-01-18 20:35:51 +00001518
reed@android.com8a1c16f2008-12-17 15:59:43 +00001519 SkScalar cosh = SkPoint::DotProduct(before, after);
1520 SkScalar sinh = SkPoint::CrossProduct(before, after);
1521
1522 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001523 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 return;
1525 }
reed@google.comabf15c12011-01-18 20:35:51 +00001526
Mike Reeda99b6ce2017-02-04 11:04:26 -05001527 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528
Mike Reeda99b6ce2017-02-04 11:04:26 -05001529 SkScalar xx = x1 - dist * before.fX;
1530 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001531 after.setLength(dist);
1532 this->lineTo(xx, yy);
1533 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1534 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535}
1536
1537///////////////////////////////////////////////////////////////////////////////
1538
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001539void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001540 SkMatrix matrix;
1541
1542 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001543 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001544}
1545
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001546void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001547 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001549 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001550 SkPoint pts[4];
1551 Verb verb;
1552
Cary Clark9480d822017-10-19 18:01:13 -04001553 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001554 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 while ((verb = iter.next(pts)) != kDone_Verb) {
1556 switch (verb) {
1557 case kMove_Verb:
1558 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001559 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1560 injectMoveToIfNeeded(); // In case last contour is closed
1561 this->lineTo(pts[0]);
1562 } else {
1563 this->moveTo(pts[0]);
1564 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 break;
1566 case kLine_Verb:
1567 proc(matrix, &pts[1], &pts[1], 1);
1568 this->lineTo(pts[1]);
1569 break;
1570 case kQuad_Verb:
1571 proc(matrix, &pts[1], &pts[1], 2);
1572 this->quadTo(pts[1], pts[2]);
1573 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001574 case kConic_Verb:
1575 proc(matrix, &pts[1], &pts[1], 2);
1576 this->conicTo(pts[1], pts[2], iter.conicWeight());
1577 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001578 case kCubic_Verb:
1579 proc(matrix, &pts[1], &pts[1], 3);
1580 this->cubicTo(pts[1], pts[2], pts[3]);
1581 break;
1582 case kClose_Verb:
1583 this->close();
1584 break;
1585 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001586 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001587 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001588 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 }
1590}
1591
1592///////////////////////////////////////////////////////////////////////////////
1593
reed@google.com277c3f82013-05-31 15:17:50 +00001594static int pts_in_verb(unsigned verb) {
1595 static const uint8_t gPtsInVerb[] = {
1596 1, // kMove
1597 1, // kLine
1598 2, // kQuad
1599 2, // kConic
1600 3, // kCubic
1601 0, // kClose
1602 0 // kDone
1603 };
1604
1605 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1606 return gPtsInVerb[verb];
1607}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001608
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609// ignore the last point of the 1st contour
1610void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001611 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1612 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001613 return;
1614 }
caryclark51c56782016-11-07 05:09:28 -08001615 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1616 SkASSERT(verbsEnd[0] == kMove_Verb);
1617 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1618 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619
caryclark51c56782016-11-07 05:09:28 -08001620 while (verbs < verbsEnd) {
1621 uint8_t v = *verbs++;
1622 pts -= pts_in_verb(v);
1623 switch (v) {
1624 case kMove_Verb:
1625 // if the path has multiple contours, stop after reversing the last
1626 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001628 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629 break;
1630 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001631 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001633 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001634 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001635 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001637 this->cubicTo(pts[2], pts[1], pts[0]);
1638 break;
1639 case kClose_Verb:
1640 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001641 break;
1642 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001643 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 break;
1645 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 }
1647}
1648
reed@google.com63d73742012-01-10 15:33:12 +00001649void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001650 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001651
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001652 const SkPoint* pts = src.fPathRef->pointsEnd();
1653 // we will iterator through src's verbs backwards
1654 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1655 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001656 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001657
1658 bool needMove = true;
1659 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 while (verbs < verbsEnd) {
1661 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001662 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001663
1664 if (needMove) {
1665 --pts;
1666 this->moveTo(pts->fX, pts->fY);
1667 needMove = false;
1668 }
1669 pts -= n;
1670 switch (v) {
1671 case kMove_Verb:
1672 if (needClose) {
1673 this->close();
1674 needClose = false;
1675 }
1676 needMove = true;
1677 pts += 1; // so we see the point in "if (needMove)" above
1678 break;
1679 case kLine_Verb:
1680 this->lineTo(pts[0]);
1681 break;
1682 case kQuad_Verb:
1683 this->quadTo(pts[1], pts[0]);
1684 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001685 case kConic_Verb:
1686 this->conicTo(pts[1], pts[0], *--conicWeights);
1687 break;
reed@google.com63d73742012-01-10 15:33:12 +00001688 case kCubic_Verb:
1689 this->cubicTo(pts[2], pts[1], pts[0]);
1690 break;
1691 case kClose_Verb:
1692 needClose = true;
1693 break;
1694 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001695 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001696 }
1697 }
1698}
1699
reed@android.com8a1c16f2008-12-17 15:59:43 +00001700///////////////////////////////////////////////////////////////////////////////
1701
1702void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1703 SkMatrix matrix;
1704
1705 matrix.setTranslate(dx, dy);
1706 this->transform(matrix, dst);
1707}
1708
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1710 int level = 2) {
1711 if (--level >= 0) {
1712 SkPoint tmp[7];
1713
1714 SkChopCubicAtHalf(pts, tmp);
1715 subdivide_cubic_to(path, &tmp[0], level);
1716 subdivide_cubic_to(path, &tmp[3], level);
1717 } else {
1718 path->cubicTo(pts[1], pts[2], pts[3]);
1719 }
1720}
1721
1722void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1723 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001724 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 dst = (SkPath*)this;
1726 }
1727
tomhudson@google.com8d430182011-06-06 19:11:19 +00001728 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 SkPath tmp;
1730 tmp.fFillType = fFillType;
1731
1732 SkPath::Iter iter(*this, false);
1733 SkPoint pts[4];
1734 SkPath::Verb verb;
1735
reed@google.com4a3b7142012-05-16 17:16:46 +00001736 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001737 switch (verb) {
1738 case kMove_Verb:
1739 tmp.moveTo(pts[0]);
1740 break;
1741 case kLine_Verb:
1742 tmp.lineTo(pts[1]);
1743 break;
1744 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001745 // promote the quad to a conic
1746 tmp.conicTo(pts[1], pts[2],
1747 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001748 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001749 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001750 tmp.conicTo(pts[1], pts[2],
1751 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001752 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 case kCubic_Verb:
1754 subdivide_cubic_to(&tmp, pts);
1755 break;
1756 case kClose_Verb:
1757 tmp.close();
1758 break;
1759 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001760 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 break;
1762 }
1763 }
1764
1765 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001766 SkPathRef::Editor ed(&dst->fPathRef);
1767 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001768 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001769 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001770 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1771
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001774 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001775 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001777
reed026beb52015-06-10 14:23:15 -07001778 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1779 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001780 } else {
1781 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001782 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1783 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001784 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001785 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1786 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001787 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001788 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001789 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001790 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001791 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 }
1793 }
1794
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 SkDEBUGCODE(dst->validate();)
1796 }
1797}
1798
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799///////////////////////////////////////////////////////////////////////////////
1800///////////////////////////////////////////////////////////////////////////////
1801
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001803 kEmptyContour_SegmentState, // The current contour is empty. We may be
1804 // starting processing or we may have just
1805 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001806 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1807 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1808 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001809};
1810
1811SkPath::Iter::Iter() {
1812#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001813 fPts = nullptr;
1814 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001817 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818#endif
1819 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001820 fVerbs = nullptr;
1821 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 fNeedClose = false;
1823}
1824
1825SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1826 this->setPath(path, forceClose);
1827}
1828
1829void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 fPts = path.fPathRef->points();
1831 fVerbs = path.fPathRef->verbs();
1832 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001833 fConicWeights = path.fPathRef->conicWeights();
1834 if (fConicWeights) {
1835 fConicWeights -= 1; // begin one behind
1836 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001838 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 fForceClose = SkToU8(forceClose);
1840 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001841 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842}
1843
1844bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001845 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 return false;
1847 }
1848 if (fForceClose) {
1849 return true;
1850 }
1851
1852 const uint8_t* verbs = fVerbs;
1853 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001854
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001855 if (kMove_Verb == *(verbs - 1)) {
1856 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857 }
1858
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001859 while (verbs > stop) {
1860 // verbs points one beyond the current verb, decrement first.
1861 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001862 if (kMove_Verb == v) {
1863 break;
1864 }
1865 if (kClose_Verb == v) {
1866 return true;
1867 }
1868 }
1869 return false;
1870}
1871
1872SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001873 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001875 // A special case: if both points are NaN, SkPoint::operation== returns
1876 // false, but the iterator expects that they are treated as the same.
1877 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001878 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1879 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001880 return kClose_Verb;
1881 }
1882
reed@google.com9e25dbf2012-05-15 17:05:38 +00001883 pts[0] = fLastPt;
1884 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001885 fLastPt = fMoveTo;
1886 fCloseLine = true;
1887 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001888 } else {
1889 pts[0] = fMoveTo;
1890 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001891 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001892}
1893
reed@google.com9e25dbf2012-05-15 17:05:38 +00001894const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 if (fSegmentState == kAfterMove_SegmentState) {
1896 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001898 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1901 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904}
1905
caryclarke8c56662015-07-14 11:19:26 -07001906void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 // We need to step over anything that will not move the current draw point
1908 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001909 const uint8_t* lastMoveVerb = nullptr;
1910 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001911 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001912 SkPoint lastPt = fLastPt;
1913 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001914 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 switch (verb) {
1916 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917 // Keep a record of this most recent move
1918 lastMoveVerb = fVerbs;
1919 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001920 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001921 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001923 fPts++;
1924 break;
1925
1926 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001927 // A close when we are in a segment is always valid except when it
1928 // follows a move which follows a segment.
1929 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 return;
1931 }
1932 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001933 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001934 break;
1935
1936 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001937 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 if (lastMoveVerb) {
1939 fVerbs = lastMoveVerb;
1940 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001941 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001942 return;
1943 }
1944 return;
1945 }
1946 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001947 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 fPts++;
1949 break;
1950
reed@google.com277c3f82013-05-31 15:17:50 +00001951 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001953 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001954 if (lastMoveVerb) {
1955 fVerbs = lastMoveVerb;
1956 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001957 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001958 return;
1959 }
1960 return;
1961 }
1962 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001963 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001964 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001965 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 break;
1967
1968 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001969 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 if (lastMoveVerb) {
1971 fVerbs = lastMoveVerb;
1972 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001973 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 return;
1975 }
1976 return;
1977 }
1978 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001979 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001980 fPts += 3;
1981 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001982
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001984 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 }
1986 }
1987}
1988
reed@google.com4a3b7142012-05-16 17:16:46 +00001989SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001990 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001991
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001993 // Close the curve if requested and if there is some curve to close
1994 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001995 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001996 return kLine_Verb;
1997 }
1998 fNeedClose = false;
1999 return kClose_Verb;
2000 }
2001 return kDone_Verb;
2002 }
2003
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002004 // fVerbs is one beyond the current verb, decrement first
2005 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002006 const SkPoint* SK_RESTRICT srcPts = fPts;
2007 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008
2009 switch (verb) {
2010 case kMove_Verb:
2011 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002012 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002013 verb = this->autoClose(pts);
2014 if (verb == kClose_Verb) {
2015 fNeedClose = false;
2016 }
2017 return (Verb)verb;
2018 }
2019 if (fVerbs == fVerbStop) { // might be a trailing moveto
2020 return kDone_Verb;
2021 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002022 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002023 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002024 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002025 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002026 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027 fNeedClose = fForceClose;
2028 break;
2029 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002030 pts[0] = this->cons_moveTo();
2031 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 fLastPt = srcPts[0];
2033 fCloseLine = false;
2034 srcPts += 1;
2035 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002036 case kConic_Verb:
2037 fConicWeights += 1;
2038 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002040 pts[0] = this->cons_moveTo();
2041 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 fLastPt = srcPts[1];
2043 srcPts += 2;
2044 break;
2045 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002046 pts[0] = this->cons_moveTo();
2047 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 fLastPt = srcPts[2];
2049 srcPts += 3;
2050 break;
2051 case kClose_Verb:
2052 verb = this->autoClose(pts);
2053 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002054 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055 } else {
2056 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002057 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002058 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002059 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060 break;
2061 }
2062 fPts = srcPts;
2063 return (Verb)verb;
2064}
2065
2066///////////////////////////////////////////////////////////////////////////////
2067
Ben Wagner4d1955c2017-03-10 13:08:15 -05002068#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002069#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002070#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002071
reed@google.com51bbe752013-01-17 22:07:50 +00002072static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002073 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002074 str->append(label);
2075 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002076
reed@google.com51bbe752013-01-17 22:07:50 +00002077 const SkScalar* values = &pts[0].fX;
2078 count *= 2;
2079
2080 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002081 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002082 if (i < count - 1) {
2083 str->append(", ");
2084 }
2085 }
Mike Reed405b9d22018-01-09 15:11:08 -05002086 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002087 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002088 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002089 }
caryclark08fa28c2014-10-23 13:08:56 -07002090 str->append(");");
reede05fed02014-12-15 07:59:53 -08002091 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002092 str->append(" // ");
2093 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002094 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002095 if (i < count - 1) {
2096 str->append(", ");
2097 }
2098 }
2099 if (conicWeight >= 0) {
2100 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002101 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002102 }
2103 }
2104 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002105}
2106
caryclarke9562592014-09-15 09:26:09 -07002107void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002108 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 Iter iter(*this, forceClose);
2110 SkPoint pts[4];
2111 Verb verb;
2112
reed@google.com51bbe752013-01-17 22:07:50 +00002113 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002114 char const * const gFillTypeStrs[] = {
2115 "Winding",
2116 "EvenOdd",
2117 "InverseWinding",
2118 "InverseEvenOdd",
2119 };
2120 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2121 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002122 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123 switch (verb) {
2124 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002125 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 break;
2127 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002128 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 break;
2130 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002131 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002133 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002134 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002135 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002137 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 break;
2139 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002140 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
2142 default:
2143 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2144 verb = kDone_Verb; // stop the loop
2145 break;
2146 }
caryclark1049f122015-04-20 08:31:59 -07002147 if (!wStream && builder.size()) {
2148 SkDebugf("%s", builder.c_str());
2149 builder.reset();
2150 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 }
caryclark66a5d8b2014-06-24 08:30:15 -07002152 if (wStream) {
2153 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002154 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155}
2156
reed@android.come522ca52009-11-23 20:10:41 +00002157void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002158 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002159}
2160
2161void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002162 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002163}
2164
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002165
Cary Clark0461e9f2017-08-25 15:13:38 -04002166bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002167 if ((fFillType & ~3) != 0) {
2168 return false;
2169 }
reed@google.comabf15c12011-01-18 20:35:51 +00002170
djsollen@google.com077348c2012-10-22 20:23:32 +00002171#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002172 if (!fBoundsIsDirty) {
2173 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002174
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002175 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002176 if (SkToBool(fIsFinite) != isFinite) {
2177 return false;
2178 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002179
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002180 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002181 // if we're empty, fBounds may be empty but translated, so we can't
2182 // necessarily compare to bounds directly
2183 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2184 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002185 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2186 return false;
2187 }
reed@android.come522ca52009-11-23 20:10:41 +00002188 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002189 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002190 if (!fBounds.isEmpty()) {
2191 return false;
2192 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002193 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002194 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002195 if (!fBounds.contains(bounds)) {
2196 return false;
2197 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002198 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002199 }
reed@android.come522ca52009-11-23 20:10:41 +00002200 }
2201 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002202#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002203 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002204}
reed@android.come522ca52009-11-23 20:10:41 +00002205
reed@google.com04863fa2011-05-15 04:08:24 +00002206///////////////////////////////////////////////////////////////////////////////
2207
reed@google.com0b7b9822011-05-16 12:29:27 +00002208static int sign(SkScalar x) { return x < 0; }
2209#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002210
robertphillipsc506e302014-09-16 09:43:31 -07002211enum DirChange {
2212 kLeft_DirChange,
2213 kRight_DirChange,
2214 kStraight_DirChange,
2215 kBackwards_DirChange,
2216
2217 kInvalid_DirChange
2218};
2219
2220
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002221static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002222 // The error epsilon was empirically derived; worse case round rects
2223 // with a mid point outset by 2x float epsilon in tests had an error
2224 // of 12.
2225 const int epsilon = 16;
2226 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2227 return false;
2228 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002229 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002230 int aBits = SkFloatAs2sCompliment(compA);
2231 int bBits = SkFloatAs2sCompliment(compB);
2232 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002233}
2234
caryclarkb4216502015-03-02 13:02:34 -08002235static bool approximately_zero_when_compared_to(double x, double y) {
2236 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002237}
2238
caryclarkb4216502015-03-02 13:02:34 -08002239
reed@google.com04863fa2011-05-15 04:08:24 +00002240// only valid for a single contour
2241struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002242 Convexicator()
2243 : fPtCount(0)
2244 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002245 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002246 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002247 , fIsCurve(false)
2248 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002249 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002250 // warnings
djsollen2f124632016-04-29 13:53:05 -07002251 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002252 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002253 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002254 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002255 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002256
2257 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002258 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002259 }
2260
2261 SkPath::Convexity getConvexity() const { return fConvexity; }
2262
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002263 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002264 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002265
reed@google.com04863fa2011-05-15 04:08:24 +00002266 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002267 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002268 return;
2269 }
2270
2271 if (0 == fPtCount) {
2272 fCurrPt = pt;
2273 ++fPtCount;
2274 } else {
2275 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002276 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002277 if (!SkScalarIsFinite(lengthSqd)) {
2278 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002279 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002280 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002281 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002282 fCurrPt = pt;
2283 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002284 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002285 } else {
2286 SkASSERT(fPtCount > 2);
2287 this->addVec(vec);
2288 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002289
reed@google.com85b6e392011-05-15 20:25:17 +00002290 int sx = sign(vec.fX);
2291 int sy = sign(vec.fY);
2292 fDx += (sx != fSx);
2293 fDy += (sy != fSy);
2294 fSx = sx;
2295 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002296
reed@google.com85b6e392011-05-15 20:25:17 +00002297 if (fDx > 3 || fDy > 3) {
2298 fConvexity = SkPath::kConcave_Convexity;
2299 }
reed@google.com04863fa2011-05-15 04:08:24 +00002300 }
2301 }
2302 }
2303
2304 void close() {
2305 if (fPtCount > 2) {
2306 this->addVec(fFirstVec);
2307 }
2308 }
2309
caryclarkb4216502015-03-02 13:02:34 -08002310 DirChange directionChange(const SkVector& curVec) {
2311 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2312
2313 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2314 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2315 largest = SkTMax(largest, -smallest);
2316
2317 if (!almost_equal(largest, largest + cross)) {
2318 int sign = SkScalarSignAsInt(cross);
2319 if (sign) {
2320 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2321 }
2322 }
2323
2324 if (cross) {
2325 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2326 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2327 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2328 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2329 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2330 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2331 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2332 if (sign) {
2333 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2334 }
2335 }
2336 }
2337
Cary Clarkdf429f32017-11-08 11:44:31 -05002338 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2339 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2340 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2341 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002342 fLastVec.dot(curVec) < 0.0f) {
2343 return kBackwards_DirChange;
2344 }
2345
2346 return kStraight_DirChange;
2347 }
2348
Cary Clarkc8323aa2017-08-25 08:04:43 -04002349 bool hasBackwards() const {
2350 return fBackwards;
2351 }
caryclarkb4216502015-03-02 13:02:34 -08002352
caryclarkd3d1a982014-12-08 04:57:38 -08002353 bool isFinite() const {
2354 return fIsFinite;
2355 }
2356
caryclark5ccef572015-03-02 10:07:56 -08002357 void setCurve(bool isCurve) {
2358 fIsCurve = isCurve;
2359 }
2360
reed@google.com04863fa2011-05-15 04:08:24 +00002361private:
2362 void addVec(const SkVector& vec) {
2363 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002364 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002365 switch (dir) {
2366 case kLeft_DirChange: // fall through
2367 case kRight_DirChange:
2368 if (kInvalid_DirChange == fExpectedDir) {
2369 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002370 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2371 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002372 } else if (dir != fExpectedDir) {
2373 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002374 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002375 }
2376 fLastVec = vec;
2377 break;
2378 case kStraight_DirChange:
2379 break;
2380 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002381 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002382 // If any of the subsequent dir is non-backward, it'll be concave.
2383 // Otherwise, it's still convex.
2384 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002385 }
robertphillipsc506e302014-09-16 09:43:31 -07002386 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002387 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002388 break;
2389 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002390 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002391 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002392 }
2393 }
2394
caryclarkb4216502015-03-02 13:02:34 -08002395 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002396 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002397 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002398 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2399 // value with the current vec is deemed to be of a significant value.
2400 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002401 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002402 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002403 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002404 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002405 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002406 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002407 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002408 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002409};
2410
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002411SkPath::Convexity SkPath::internalGetConvexity() const {
2412 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002413 SkPoint pts[4];
2414 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002415 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002416
2417 int contourCount = 0;
2418 int count;
2419 Convexicator state;
2420
caryclarkd3d1a982014-12-08 04:57:38 -08002421 if (!isFinite()) {
2422 return kUnknown_Convexity;
2423 }
Brian Osman205a1262017-09-18 13:13:48 +00002424 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002425 switch (verb) {
2426 case kMove_Verb:
2427 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002428 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002429 return kConcave_Convexity;
2430 }
2431 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002432 // fall through
2433 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002434 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002435 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002436 break;
caryclark5ccef572015-03-02 10:07:56 -08002437 case kQuad_Verb:
2438 // fall through
2439 case kConic_Verb:
2440 // fall through
2441 case kCubic_Verb:
2442 count = 2 + (kCubic_Verb == verb);
2443 // As an additional enhancement, this could set curve true only
2444 // if the curve is nonlinear
2445 state.setCurve(true);
2446 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002447 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002448 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002449 state.close();
2450 count = 0;
2451 break;
2452 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002453 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002454 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002455 return kConcave_Convexity;
2456 }
2457
2458 for (int i = 1; i <= count; i++) {
2459 state.addPt(pts[i]);
2460 }
2461 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002462 if (!state.isFinite()) {
2463 return kUnknown_Convexity;
2464 }
reed@google.com04863fa2011-05-15 04:08:24 +00002465 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002466 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002467 return kConcave_Convexity;
2468 }
2469 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002470 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002471 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002472 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2473 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2474 fConvexity = Convexity::kConcave_Convexity;
2475 } else {
2476 fFirstDirection = state.getFirstDirection();
2477 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002478 }
2479 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002480}
reed@google.com69a99432012-01-10 18:00:10 +00002481
2482///////////////////////////////////////////////////////////////////////////////
2483
2484class ContourIter {
2485public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002486 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002487
2488 bool done() const { return fDone; }
2489 // if !done() then these may be called
2490 int count() const { return fCurrPtCount; }
2491 const SkPoint* pts() const { return fCurrPt; }
2492 void next();
2493
2494private:
2495 int fCurrPtCount;
2496 const SkPoint* fCurrPt;
2497 const uint8_t* fCurrVerb;
2498 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002499 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002500 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002501 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002502};
2503
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002504ContourIter::ContourIter(const SkPathRef& pathRef) {
2505 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002506 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002507 fCurrPt = pathRef.points();
2508 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002509 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002510 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002511 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002512 this->next();
2513}
2514
2515void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002516 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002517 fDone = true;
2518 }
2519 if (fDone) {
2520 return;
2521 }
2522
2523 // skip pts of prev contour
2524 fCurrPt += fCurrPtCount;
2525
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002526 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002527 int ptCount = 1; // moveTo
2528 const uint8_t* verbs = fCurrVerb;
2529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002530 for (--verbs; verbs > fStopVerbs; --verbs) {
2531 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002532 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002533 goto CONTOUR_END;
2534 case SkPath::kLine_Verb:
2535 ptCount += 1;
2536 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002537 case SkPath::kConic_Verb:
2538 fCurrConicWeight += 1;
2539 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002540 case SkPath::kQuad_Verb:
2541 ptCount += 2;
2542 break;
2543 case SkPath::kCubic_Verb:
2544 ptCount += 3;
2545 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002546 case SkPath::kClose_Verb:
2547 break;
2548 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002549 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002550 break;
2551 }
2552 }
2553CONTOUR_END:
2554 fCurrPtCount = ptCount;
2555 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002556 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002557}
2558
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002559// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002560static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002561 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2562 // We may get 0 when the above subtracts underflow. We expect this to be
2563 // very rare and lazily promote to double.
2564 if (0 == cross) {
2565 double p0x = SkScalarToDouble(p0.fX);
2566 double p0y = SkScalarToDouble(p0.fY);
2567
2568 double p1x = SkScalarToDouble(p1.fX);
2569 double p1y = SkScalarToDouble(p1.fY);
2570
2571 double p2x = SkScalarToDouble(p2.fX);
2572 double p2y = SkScalarToDouble(p2.fY);
2573
2574 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2575 (p1y - p0y) * (p2x - p0x));
2576
2577 }
2578 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002579}
2580
reed@google.comc1ea60a2012-01-31 15:15:36 +00002581// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002582static int find_max_y(const SkPoint pts[], int count) {
2583 SkASSERT(count > 0);
2584 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002585 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002586 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002587 SkScalar y = pts[i].fY;
2588 if (y > max) {
2589 max = y;
2590 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002591 }
2592 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002593 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002594}
2595
reed@google.comcabaf1d2012-01-11 21:03:05 +00002596static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2597 int i = index;
2598 for (;;) {
2599 i = (i + inc) % n;
2600 if (i == index) { // we wrapped around, so abort
2601 break;
2602 }
2603 if (pts[index] != pts[i]) { // found a different point, success!
2604 break;
2605 }
2606 }
2607 return i;
2608}
2609
reed@google.comc1ea60a2012-01-31 15:15:36 +00002610/**
2611 * Starting at index, and moving forward (incrementing), find the xmin and
2612 * xmax of the contiguous points that have the same Y.
2613 */
2614static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2615 int* maxIndexPtr) {
2616 const SkScalar y = pts[index].fY;
2617 SkScalar min = pts[index].fX;
2618 SkScalar max = min;
2619 int minIndex = index;
2620 int maxIndex = index;
2621 for (int i = index + 1; i < n; ++i) {
2622 if (pts[i].fY != y) {
2623 break;
2624 }
2625 SkScalar x = pts[i].fX;
2626 if (x < min) {
2627 min = x;
2628 minIndex = i;
2629 } else if (x > max) {
2630 max = x;
2631 maxIndex = i;
2632 }
2633 }
2634 *maxIndexPtr = maxIndex;
2635 return minIndex;
2636}
2637
reed026beb52015-06-10 14:23:15 -07002638static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2639 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002640}
2641
reed@google.comac8543f2012-01-30 20:51:25 +00002642/*
2643 * We loop through all contours, and keep the computed cross-product of the
2644 * contour that contained the global y-max. If we just look at the first
2645 * contour, we may find one that is wound the opposite way (correctly) since
2646 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2647 * that is outer most (or at least has the global y-max) before we can consider
2648 * its cross product.
2649 */
reed026beb52015-06-10 14:23:15 -07002650bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002651 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2652 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002653 return true;
2654 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002655
2656 // don't want to pay the cost for computing this if it
2657 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002658 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2659 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002660 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002661 return false;
2662 }
reed@google.com69a99432012-01-10 18:00:10 +00002663
reed026beb52015-06-10 14:23:15 -07002664 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002665
reed@google.comac8543f2012-01-30 20:51:25 +00002666 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002667 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002668 SkScalar ymaxCross = 0;
2669
reed@google.com69a99432012-01-10 18:00:10 +00002670 for (; !iter.done(); iter.next()) {
2671 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002672 if (n < 3) {
2673 continue;
2674 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002675
reed@google.comcabaf1d2012-01-11 21:03:05 +00002676 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002677 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002678 int index = find_max_y(pts, n);
2679 if (pts[index].fY < ymax) {
2680 continue;
2681 }
2682
2683 // If there is more than 1 distinct point at the y-max, we take the
2684 // x-min and x-max of them and just subtract to compute the dir.
2685 if (pts[(index + 1) % n].fY == pts[index].fY) {
2686 int maxIndex;
2687 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2688 if (minIndex == maxIndex) {
2689 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002690 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002691 SkASSERT(pts[minIndex].fY == pts[index].fY);
2692 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2693 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2694 // we just subtract the indices, and let that auto-convert to
2695 // SkScalar, since we just want - or + to signal the direction.
2696 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002697 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002698 TRY_CROSSPROD:
2699 // Find a next and prev index to use for the cross-product test,
2700 // but we try to find pts that form non-zero vectors from pts[index]
2701 //
2702 // Its possible that we can't find two non-degenerate vectors, so
2703 // we have to guard our search (e.g. all the pts could be in the
2704 // same place).
2705
2706 // we pass n - 1 instead of -1 so we don't foul up % operator by
2707 // passing it a negative LH argument.
2708 int prev = find_diff_pt(pts, index, n, n - 1);
2709 if (prev == index) {
2710 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002711 continue;
2712 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002713 int next = find_diff_pt(pts, index, n, 1);
2714 SkASSERT(next != index);
2715 cross = cross_prod(pts[prev], pts[index], pts[next]);
2716 // if we get a zero and the points are horizontal, then we look at the spread in
2717 // x-direction. We really should continue to walk away from the degeneracy until
2718 // there is a divergence.
2719 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2720 // construct the subtract so we get the correct Direction below
2721 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002722 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002723 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002724
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002725 if (cross) {
2726 // record our best guess so far
2727 ymax = pts[index].fY;
2728 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002729 }
2730 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002731 if (ymaxCross) {
2732 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002733 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002734 return true;
2735 } else {
2736 return false;
2737 }
reed@google.comac8543f2012-01-30 20:51:25 +00002738}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002739
2740///////////////////////////////////////////////////////////////////////////////
2741
caryclark9aacd902015-12-14 08:38:09 -08002742static bool between(SkScalar a, SkScalar b, SkScalar c) {
2743 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2744 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2745 return (a - b) * (c - b) <= 0;
2746}
2747
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002748static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2749 SkScalar t) {
2750 SkScalar A = c3 + 3*(c1 - c2) - c0;
2751 SkScalar B = 3*(c2 - c1 - c1 + c0);
2752 SkScalar C = 3*(c1 - c0);
2753 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002754 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755}
2756
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002757template <size_t N> static void find_minmax(const SkPoint pts[],
2758 SkScalar* minPtr, SkScalar* maxPtr) {
2759 SkScalar min, max;
2760 min = max = pts[0].fX;
2761 for (size_t i = 1; i < N; ++i) {
2762 min = SkMinScalar(min, pts[i].fX);
2763 max = SkMaxScalar(max, pts[i].fX);
2764 }
2765 *minPtr = min;
2766 *maxPtr = max;
2767}
2768
caryclark9cb5d752015-12-18 04:35:24 -08002769static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2770 if (start.fY == end.fY) {
2771 return between(start.fX, x, end.fX) && x != end.fX;
2772 } else {
2773 return x == start.fX && y == start.fY;
2774 }
2775}
2776
caryclark9aacd902015-12-14 08:38:09 -08002777static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002778 SkScalar y0 = pts[0].fY;
2779 SkScalar y3 = pts[3].fY;
2780
2781 int dir = 1;
2782 if (y0 > y3) {
2783 SkTSwap(y0, y3);
2784 dir = -1;
2785 }
2786 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002787 return 0;
2788 }
caryclark9cb5d752015-12-18 04:35:24 -08002789 if (checkOnCurve(x, y, pts[0], pts[3])) {
2790 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002791 return 0;
2792 }
caryclark9cb5d752015-12-18 04:35:24 -08002793 if (y == y3) {
2794 return 0;
2795 }
2796
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002797 // quickreject or quickaccept
2798 SkScalar min, max;
2799 find_minmax<4>(pts, &min, &max);
2800 if (x < min) {
2801 return 0;
2802 }
2803 if (x > max) {
2804 return dir;
2805 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002806
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002807 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002808 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002809 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002810 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002811 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002812 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002813 if (SkScalarNearlyEqual(xt, x)) {
2814 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2815 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002816 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002817 }
2818 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002819 return xt < x ? dir : 0;
2820}
2821
caryclark9aacd902015-12-14 08:38:09 -08002822static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002823 SkPoint dst[10];
2824 int n = SkChopCubicAtYExtrema(pts, dst);
2825 int w = 0;
2826 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002827 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 }
2829 return w;
2830}
2831
caryclark9aacd902015-12-14 08:38:09 -08002832static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2833 SkASSERT(src);
2834 SkASSERT(t >= 0 && t <= 1);
2835 SkScalar src2w = src[2] * w;
2836 SkScalar C = src[0];
2837 SkScalar A = src[4] - 2 * src2w + C;
2838 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002839 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002840}
2841
2842
2843static double conic_eval_denominator(SkScalar w, SkScalar t) {
2844 SkScalar B = 2 * (w - 1);
2845 SkScalar C = 1;
2846 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002847 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002848}
2849
2850static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2851 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 SkScalar y0 = pts[0].fY;
2853 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002854
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 int dir = 1;
2856 if (y0 > y2) {
2857 SkTSwap(y0, y2);
2858 dir = -1;
2859 }
caryclark9aacd902015-12-14 08:38:09 -08002860 if (y < y0 || y > y2) {
2861 return 0;
2862 }
caryclark9cb5d752015-12-18 04:35:24 -08002863 if (checkOnCurve(x, y, pts[0], pts[2])) {
2864 *onCurveCount += 1;
2865 return 0;
2866 }
caryclark9aacd902015-12-14 08:38:09 -08002867 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002868 return 0;
2869 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002870
caryclark9aacd902015-12-14 08:38:09 -08002871 SkScalar roots[2];
2872 SkScalar A = pts[2].fY;
2873 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2874 SkScalar C = pts[0].fY;
2875 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2876 B -= C; // B = b*w - w * yCept + yCept - a
2877 C -= y;
2878 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2879 SkASSERT(n <= 1);
2880 SkScalar xt;
2881 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002882 // zero roots are returned only when y0 == y
2883 // Need [0] if dir == 1
2884 // and [2] if dir == -1
2885 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002886 } else {
2887 SkScalar t = roots[0];
2888 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2889 }
2890 if (SkScalarNearlyEqual(xt, x)) {
2891 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2892 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002893 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002894 }
2895 }
2896 return xt < x ? dir : 0;
2897}
2898
2899static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2900 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2901 if (y0 == y1) {
2902 return true;
2903 }
2904 if (y0 < y1) {
2905 return y1 <= y2;
2906 } else {
2907 return y1 >= y2;
2908 }
2909}
2910
2911static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2912 int* onCurveCount) {
2913 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002914 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002915 // If the data points are very large, the conic may not be monotonic but may also
2916 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002917 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2918 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2919 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002920 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2921 }
2922 return w;
2923}
2924
2925static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2926 SkScalar y0 = pts[0].fY;
2927 SkScalar y2 = pts[2].fY;
2928
2929 int dir = 1;
2930 if (y0 > y2) {
2931 SkTSwap(y0, y2);
2932 dir = -1;
2933 }
2934 if (y < y0 || y > y2) {
2935 return 0;
2936 }
caryclark9cb5d752015-12-18 04:35:24 -08002937 if (checkOnCurve(x, y, pts[0], pts[2])) {
2938 *onCurveCount += 1;
2939 return 0;
2940 }
caryclark9aacd902015-12-14 08:38:09 -08002941 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002942 return 0;
2943 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002944 // bounds check on X (not required. is it faster?)
2945#if 0
2946 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2947 return 0;
2948 }
2949#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002950
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 SkScalar roots[2];
2952 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2953 2 * (pts[1].fY - pts[0].fY),
2954 pts[0].fY - y,
2955 roots);
2956 SkASSERT(n <= 1);
2957 SkScalar xt;
2958 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002959 // zero roots are returned only when y0 == y
2960 // Need [0] if dir == 1
2961 // and [2] if dir == -1
2962 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002963 } else {
2964 SkScalar t = roots[0];
2965 SkScalar C = pts[0].fX;
2966 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2967 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002968 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002969 }
caryclark9aacd902015-12-14 08:38:09 -08002970 if (SkScalarNearlyEqual(xt, x)) {
2971 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2972 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002973 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002974 }
2975 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 return xt < x ? dir : 0;
2977}
2978
caryclark9aacd902015-12-14 08:38:09 -08002979static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002980 SkPoint dst[5];
2981 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002982
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2984 n = SkChopQuadAtYExtrema(pts, dst);
2985 pts = dst;
2986 }
caryclark9aacd902015-12-14 08:38:09 -08002987 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002988 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002989 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002990 }
2991 return w;
2992}
2993
caryclark9aacd902015-12-14 08:38:09 -08002994static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 SkScalar x0 = pts[0].fX;
2996 SkScalar y0 = pts[0].fY;
2997 SkScalar x1 = pts[1].fX;
2998 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002999
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003000 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003001
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 int dir = 1;
3003 if (y0 > y1) {
3004 SkTSwap(y0, y1);
3005 dir = -1;
3006 }
caryclark9aacd902015-12-14 08:38:09 -08003007 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 return 0;
3009 }
caryclark9cb5d752015-12-18 04:35:24 -08003010 if (checkOnCurve(x, y, pts[0], pts[1])) {
3011 *onCurveCount += 1;
3012 return 0;
3013 }
3014 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003015 return 0;
3016 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003017 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003018
caryclark9aacd902015-12-14 08:38:09 -08003019 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003020 // zero cross means the point is on the line, and since the case where
3021 // y of the query point is at the end point is handled above, we can be
3022 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003023 if (x != x1 || y != pts[1].fY) {
3024 *onCurveCount += 1;
3025 }
caryclark9aacd902015-12-14 08:38:09 -08003026 dir = 0;
3027 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003028 dir = 0;
3029 }
3030 return dir;
3031}
3032
caryclark9aacd902015-12-14 08:38:09 -08003033static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3034 SkTDArray<SkVector>* tangents) {
3035 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3036 && !between(pts[2].fY, y, pts[3].fY)) {
3037 return;
3038 }
3039 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3040 && !between(pts[2].fX, x, pts[3].fX)) {
3041 return;
3042 }
3043 SkPoint dst[10];
3044 int n = SkChopCubicAtYExtrema(pts, dst);
3045 for (int i = 0; i <= n; ++i) {
3046 SkPoint* c = &dst[i * 3];
3047 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003048 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003049 continue;
mbarbella276e6332016-05-31 14:44:01 -07003050 }
caryclark9aacd902015-12-14 08:38:09 -08003051 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3052 if (!SkScalarNearlyEqual(x, xt)) {
3053 continue;
3054 }
3055 SkVector tangent;
3056 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3057 tangents->push(tangent);
3058 }
3059}
3060
3061static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3062 SkTDArray<SkVector>* tangents) {
3063 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3064 return;
3065 }
3066 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3067 return;
3068 }
3069 SkScalar roots[2];
3070 SkScalar A = pts[2].fY;
3071 SkScalar B = pts[1].fY * w - y * w + y;
3072 SkScalar C = pts[0].fY;
3073 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3074 B -= C; // B = b*w - w * yCept + yCept - a
3075 C -= y;
3076 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3077 for (int index = 0; index < n; ++index) {
3078 SkScalar t = roots[index];
3079 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3080 if (!SkScalarNearlyEqual(x, xt)) {
3081 continue;
3082 }
3083 SkConic conic(pts, w);
3084 tangents->push(conic.evalTangentAt(t));
3085 }
3086}
3087
3088static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3089 SkTDArray<SkVector>* tangents) {
3090 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3091 return;
3092 }
3093 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3094 return;
3095 }
3096 SkScalar roots[2];
3097 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3098 2 * (pts[1].fY - pts[0].fY),
3099 pts[0].fY - y,
3100 roots);
3101 for (int index = 0; index < n; ++index) {
3102 SkScalar t = roots[index];
3103 SkScalar C = pts[0].fX;
3104 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3105 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003106 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003107 if (!SkScalarNearlyEqual(x, xt)) {
3108 continue;
3109 }
3110 tangents->push(SkEvalQuadTangentAt(pts, t));
3111 }
3112}
3113
3114static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3115 SkTDArray<SkVector>* tangents) {
3116 SkScalar y0 = pts[0].fY;
3117 SkScalar y1 = pts[1].fY;
3118 if (!between(y0, y, y1)) {
3119 return;
3120 }
3121 SkScalar x0 = pts[0].fX;
3122 SkScalar x1 = pts[1].fX;
3123 if (!between(x0, x, x1)) {
3124 return;
3125 }
3126 SkScalar dx = x1 - x0;
3127 SkScalar dy = y1 - y0;
3128 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3129 return;
3130 }
3131 SkVector v;
3132 v.set(dx, dy);
3133 tangents->push(v);
3134}
3135
reed@google.com4db592c2013-10-30 17:39:43 +00003136static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3137 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3138}
3139
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003140bool SkPath::contains(SkScalar x, SkScalar y) const {
3141 bool isInverse = this->isInverseFillType();
3142 if (this->isEmpty()) {
3143 return isInverse;
3144 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003145
reed@google.com4db592c2013-10-30 17:39:43 +00003146 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003147 return isInverse;
3148 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003149
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003150 SkPath::Iter iter(*this, true);
3151 bool done = false;
3152 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003153 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003154 do {
3155 SkPoint pts[4];
3156 switch (iter.next(pts, false)) {
3157 case SkPath::kMove_Verb:
3158 case SkPath::kClose_Verb:
3159 break;
3160 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003161 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003162 break;
3163 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003164 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003165 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003166 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003167 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003168 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003169 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003170 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003171 break;
3172 case SkPath::kDone_Verb:
3173 done = true;
3174 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003175 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003177 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3178 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3179 if (evenOddFill) {
3180 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003181 }
caryclark9aacd902015-12-14 08:38:09 -08003182 if (w) {
3183 return !isInverse;
3184 }
3185 if (onCurveCount <= 1) {
3186 return SkToBool(onCurveCount) ^ isInverse;
3187 }
3188 if ((onCurveCount & 1) || evenOddFill) {
3189 return SkToBool(onCurveCount & 1) ^ isInverse;
3190 }
halcanary9d524f22016-03-29 09:03:52 -07003191 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003192 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3193 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003194 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003195 SkTDArray<SkVector> tangents;
3196 do {
3197 SkPoint pts[4];
3198 int oldCount = tangents.count();
3199 switch (iter.next(pts, false)) {
3200 case SkPath::kMove_Verb:
3201 case SkPath::kClose_Verb:
3202 break;
3203 case SkPath::kLine_Verb:
3204 tangent_line(pts, x, y, &tangents);
3205 break;
3206 case SkPath::kQuad_Verb:
3207 tangent_quad(pts, x, y, &tangents);
3208 break;
3209 case SkPath::kConic_Verb:
3210 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3211 break;
3212 case SkPath::kCubic_Verb:
3213 tangent_cubic(pts, x, y, &tangents);
3214 break;
3215 case SkPath::kDone_Verb:
3216 done = true;
3217 break;
3218 }
3219 if (tangents.count() > oldCount) {
3220 int last = tangents.count() - 1;
3221 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003222 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003223 tangents.remove(last);
3224 } else {
3225 for (int index = 0; index < last; ++index) {
3226 const SkVector& test = tangents[index];
3227 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003228 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3229 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003230 tangents.remove(last);
3231 tangents.removeShuffle(index);
3232 break;
3233 }
3234 }
3235 }
3236 }
3237 } while (!done);
3238 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003239}
fmalitaaa0df4e2015-12-01 09:13:23 -08003240
3241int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3242 SkScalar w, SkPoint pts[], int pow2) {
3243 const SkConic conic(p0, p1, p2, w);
3244 return conic.chopIntoQuadsPOW2(pts, pow2);
3245}
bsalomonedc743a2016-06-01 09:42:31 -07003246
3247bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3248 unsigned* start) {
3249 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3250 return false;
3251 }
3252 SkPath::RawIter iter(path);
3253 SkPoint verbPts[4];
3254 SkPath::Verb v;
3255 SkPoint rectPts[5];
3256 int rectPtCnt = 0;
3257 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3258 switch (v) {
3259 case SkPath::kMove_Verb:
3260 if (0 != rectPtCnt) {
3261 return false;
3262 }
3263 rectPts[0] = verbPts[0];
3264 ++rectPtCnt;
3265 break;
3266 case SkPath::kLine_Verb:
3267 if (5 == rectPtCnt) {
3268 return false;
3269 }
3270 rectPts[rectPtCnt] = verbPts[1];
3271 ++rectPtCnt;
3272 break;
3273 case SkPath::kClose_Verb:
3274 if (4 == rectPtCnt) {
3275 rectPts[4] = rectPts[0];
3276 rectPtCnt = 5;
3277 }
3278 break;
3279 default:
3280 return false;
3281 }
3282 }
3283 if (rectPtCnt < 5) {
3284 return false;
3285 }
3286 if (rectPts[0] != rectPts[4]) {
3287 return false;
3288 }
bsalomon057ae8a2016-07-24 05:37:26 -07003289 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3290 // and pts 1 and 2 the opposite vertical or horizontal edge).
3291 bool vec03IsVertical;
3292 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3293 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3294 // Make sure it has non-zero width and height
3295 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003296 return false;
3297 }
bsalomon057ae8a2016-07-24 05:37:26 -07003298 vec03IsVertical = true;
3299 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3300 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3301 // Make sure it has non-zero width and height
3302 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3303 return false;
3304 }
3305 vec03IsVertical = false;
3306 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003307 return false;
3308 }
bsalomon057ae8a2016-07-24 05:37:26 -07003309 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3310 // set if it is on the bottom edge.
3311 unsigned sortFlags =
3312 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3313 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3314 switch (sortFlags) {
3315 case 0b00:
3316 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3317 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3318 *start = 0;
3319 break;
3320 case 0b01:
3321 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3322 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3323 *start = 1;
3324 break;
3325 case 0b10:
3326 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3327 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3328 *start = 3;
3329 break;
3330 case 0b11:
3331 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3332 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3333 *start = 2;
3334 break;
bsalomonedc743a2016-06-01 09:42:31 -07003335 }
bsalomonedc743a2016-06-01 09:42:31 -07003336 return true;
3337}
bsalomon21af9ca2016-08-25 12:29:23 -07003338
3339void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3340 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3341 SkASSERT(!oval.isEmpty());
3342 SkASSERT(sweepAngle);
3343
3344 path->reset();
3345 path->setIsVolatile(true);
3346 path->setFillType(SkPath::kWinding_FillType);
3347 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3348 path->addOval(oval);
3349 return;
3350 }
3351 if (useCenter) {
3352 path->moveTo(oval.centerX(), oval.centerY());
3353 }
3354 // Arc to mods at 360 and drawArc is not supposed to.
3355 bool forceMoveTo = !useCenter;
3356 while (sweepAngle <= -360.f) {
3357 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3358 startAngle -= 180.f;
3359 path->arcTo(oval, startAngle, -180.f, false);
3360 startAngle -= 180.f;
3361 forceMoveTo = false;
3362 sweepAngle += 360.f;
3363 }
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 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3373 if (useCenter) {
3374 path->close();
3375 }
3376}
Mike Reed0d7dac82017-02-02 17:45:56 -08003377
3378///////////////////////////////////////////////////////////////////////////////////////////////////
3379#include "SkNx.h"
3380
3381static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3382 SkScalar ts[2];
3383 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3384 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3385 SkASSERT(n >= 0 && n <= 2);
3386 for (int i = 0; i < n; ++i) {
3387 extremas[i] = SkEvalQuadAt(src, ts[i]);
3388 }
3389 extremas[n] = src[2];
3390 return n + 1;
3391}
3392
3393static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3394 SkConic conic(src[0], src[1], src[2], w);
3395 SkScalar ts[2];
3396 int n = conic.findXExtrema(ts);
3397 n += conic.findYExtrema(&ts[n]);
3398 SkASSERT(n >= 0 && n <= 2);
3399 for (int i = 0; i < n; ++i) {
3400 extremas[i] = conic.evalAt(ts[i]);
3401 }
3402 extremas[n] = src[2];
3403 return n + 1;
3404}
3405
3406static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3407 SkScalar ts[4];
3408 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3409 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3410 SkASSERT(n >= 0 && n <= 4);
3411 for (int i = 0; i < n; ++i) {
3412 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3413 }
3414 extremas[n] = src[3];
3415 return n + 1;
3416}
3417
Mike Reed8d3196b2017-02-03 11:34:13 -05003418SkRect SkPath::computeTightBounds() const {
3419 if (0 == this->countVerbs()) {
3420 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003421 }
3422
Mike Reed8d3196b2017-02-03 11:34:13 -05003423 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3424 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003425 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003426
Mike Reed0d7dac82017-02-02 17:45:56 -08003427 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3428 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003429 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003430
3431 // initial with the first MoveTo, so we don't have to check inside the switch
3432 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003433 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003434 for (;;) {
3435 int count = 0;
3436 switch (iter.next(pts)) {
3437 case SkPath::kMove_Verb:
3438 extremas[0] = pts[0];
3439 count = 1;
3440 break;
3441 case SkPath::kLine_Verb:
3442 extremas[0] = pts[1];
3443 count = 1;
3444 break;
3445 case SkPath::kQuad_Verb:
3446 count = compute_quad_extremas(pts, extremas);
3447 break;
3448 case SkPath::kConic_Verb:
3449 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3450 break;
3451 case SkPath::kCubic_Verb:
3452 count = compute_cubic_extremas(pts, extremas);
3453 break;
3454 case SkPath::kClose_Verb:
3455 break;
3456 case SkPath::kDone_Verb:
3457 goto DONE;
3458 }
3459 for (int i = 0; i < count; ++i) {
3460 Sk2s tmp = from_point(extremas[i]);
3461 min = Sk2s::Min(min, tmp);
3462 max = Sk2s::Max(max, tmp);
3463 }
3464 }
3465DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003466 SkRect bounds;
3467 min.store((SkPoint*)&bounds.fLeft);
3468 max.store((SkPoint*)&bounds.fRight);
3469 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003470}
Cary Clarkdf429f32017-11-08 11:44:31 -05003471
3472bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3473 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3474}
3475
3476bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3477 const SkPoint& p3, bool exact) {
3478 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3479 SkPointPriv::EqualsWithinTolerance(p2, p3);
3480}
3481
3482bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3483 const SkPoint& p3, const SkPoint& p4, bool exact) {
3484 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3485 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3486 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3487 SkPointPriv::EqualsWithinTolerance(p3, p4);
3488}