blob: 54d7a69177aee18534ce679853720cb7da5792ab [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050019#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020
Mike Reeda99b6ce2017-02-04 11:04:26 -050021static float poly_eval(float A, float B, float C, float t) {
22 return (A * t + B) * t + C;
23}
24
25static float poly_eval(float A, float B, float C, float D, float t) {
26 return ((A * t + B) * t + C) * t + D;
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029////////////////////////////////////////////////////////////////////////////
30
reed@google.com3563c9e2011-11-14 19:34:57 +000031/**
32 * Path.bounds is defined to be the bounds of all the control points.
33 * If we called bounds.join(r) we would skip r if r was empty, which breaks
34 * our promise. Hence we have a custom joiner that doesn't look at emptiness
35 */
36static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
37 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
38 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
39 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
40 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
41}
42
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043static bool is_degenerate(const SkPath& path) {
44 SkPath::Iter iter(path, false);
45 SkPoint pts[4];
46 return SkPath::kDone_Verb == iter.next(pts);
47}
48
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049class SkAutoDisableDirectionCheck {
50public:
51 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070052 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070056 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057 }
58
59private:
reed026beb52015-06-10 14:23:15 -070060 SkPath* fPath;
61 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000063#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065/* This guy's constructor/destructor bracket a path editing operation. It is
66 used when we know the bounds of the amount we are going to add to the path
67 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000070 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000071 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000072
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000073 It also notes if the path was originally degenerate, and if so, sets
74 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000075 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 */
77class SkAutoPathBoundsUpdate {
78public:
79 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
80 this->init(path);
81 }
82
83 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
84 SkScalar right, SkScalar bottom) {
85 fRect.set(left, top, right, bottom);
86 this->init(path);
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000090 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
91 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050092 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 }
95 }
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 SkPath* fPath;
99 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 // Cannot use fRect for our bounds unless we know it is sorted
106 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000108 // Mark the path's bounds as dirty if (1) they are, or (2) the path
109 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 if (fHasValidBounds && !fEmpty) {
113 joinNoEmptyChecks(&fRect, fPath->getBounds());
114 }
115 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 }
117};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000118#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000139SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800140 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700142 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400143 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144}
145
146void SkPath::resetFields() {
147 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000148 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000150 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700151 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000152
153 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700154 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 this->copyFields(that);
160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 }
174 SkDEBUGCODE(this->validate();)
175 return *this;
176}
177
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178void SkPath::copyFields(const SkPath& that) {
179 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000180 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400183 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400184
185 // Non-atomic assignment of atomic values.
186 fConvexity .store(that.fConvexity .load());
187 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000193 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000194 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700199 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000200 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400203
204 // Non-atomic swaps of atomic values.
205 Convexity c = fConvexity.load();
206 fConvexity.store(that.fConvexity.load());
207 that.fConvexity.store(c);
208
209 uint8_t fd = fFirstDirection.load();
210 fFirstDirection.store(that.fFirstDirection.load());
211 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
caryclark8e7b19d2016-02-18 04:11:48 -0800215bool SkPath::isInterpolatable(const SkPath& compare) const {
216 int count = fPathRef->countVerbs();
217 if (count != compare.fPathRef->countVerbs()) {
218 return false;
219 }
220 if (!count) {
221 return true;
222 }
223 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
224 count)) {
225 return false;
226 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800227 return !fPathRef->countWeights() ||
228 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800229 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
230}
231
232bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500233 int pointCount = fPathRef->countPoints();
234 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800235 return false;
236 }
Cary Clark7487ec82018-03-06 09:30:46 -0500237 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800238 return true;
239 }
caryclark8e7b19d2016-02-18 04:11:48 -0800240 out->reset();
241 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700242 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return true;
244}
245
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246static inline bool check_edge_against_rect(const SkPoint& p0,
247 const SkPoint& p1,
248 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700249 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 const SkPoint* edgeBegin;
251 SkVector v;
reed026beb52015-06-10 14:23:15 -0700252 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 v = p1 - p0;
254 edgeBegin = &p0;
255 } else {
256 v = p0 - p1;
257 edgeBegin = &p1;
258 }
259 if (v.fX || v.fY) {
260 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500261 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
262 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
263 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
264 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
266 return false;
267 }
268 }
269 return true;
270}
271
272bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
273 // This only handles non-degenerate convex paths currently.
274 if (kConvex_Convexity != this->getConvexity()) {
275 return false;
276 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000277
reed026beb52015-06-10 14:23:15 -0700278 SkPathPriv::FirstDirection direction;
279 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 return false;
281 }
282
283 SkPoint firstPt;
284 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500285 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkPath::Verb verb;
287 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400288 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 int nextPt = -1;
294 switch (verb) {
295 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(++moveCnt);
298 firstPt = prevPt = pts[0];
299 break;
300 case kLine_Verb:
301 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 break;
305 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000306 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 2;
310 break;
311 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 3;
315 break;
316 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 break;
319 default:
320 SkDEBUGFAIL("unknown verb");
321 }
322 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800323 if (SkPath::kConic_Verb == verb) {
324 SkConic orig;
325 orig.set(pts, iter.conicWeight());
326 SkPoint quadPts[5];
327 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800328 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800329
330 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
331 return false;
332 }
333 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
334 return false;
335 }
336 } else {
337 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
338 return false;
339 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400345 if (segmentCount) {
346 return check_edge_against_rect(prevPt, firstPt, rect, direction);
347 }
348 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000349}
350
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000351uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800353#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400354 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
355 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000356#endif
357 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000358}
djsollen@google.come63793a2012-03-21 15:39:03 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360void SkPath::reset() {
361 SkDEBUGCODE(this->validate();)
362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000364 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
367void SkPath::rewind() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
fsb1475b02016-01-20 09:51:07 -0800374bool SkPath::isLastContourClosed() const {
375 int verbCount = fPathRef->countVerbs();
376 if (0 == verbCount) {
377 return false;
378 }
379 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
380}
381
reed@google.com7e6c4d12012-05-10 14:05:43 +0000382bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000385 if (2 == verbCount) {
386 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
387 if (kLine_Verb == fPathRef->atVerb(1)) {
388 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391 line[0] = pts[0];
392 line[1] = pts[1];
393 }
394 return true;
395 }
396 }
397 return false;
398}
399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400/*
401 Determines if path is a rect by keeping track of changes in direction
402 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 The direction is computed such that:
405 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000408 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410A rectangle cycles up/right/down/left or up/left/down/right.
411
412The test fails if:
413 The path is closed, and followed by a line.
414 A second move creates a new endpoint.
415 A diagonal line is parsed.
416 There's more than four changes of direction.
417 There's a discontinuity on the line (e.g., a move in the middle)
418 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 *The rectangle doesn't complete a cycle.
422 *The final point isn't equal to the first point.
423
424 *These last two conditions we relax if we have a 3-edge path that would
425 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427It's OK if the path has:
428 Several colinear line segments composing a rectangle side.
429 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431The direction takes advantage of the corners found since opposite sides
432must travel in opposite directions.
433
434FIXME: Allow colinear quads and cubics to be treated like lines.
435FIXME: If the API passes fill-only, return true if the filled stroke
436 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000437
Cary Clark48c464a2018-04-16 12:06:07 -0400438 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000439 0x1 is set if the segment is horizontal
440 0x2 is set if the segment is moving to the right or down
441 thus:
442 two directions are opposites iff (dirA ^ dirB) == 0x2
443 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000444
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446static int rect_make_dir(SkScalar dx, SkScalar dy) {
447 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
448}
Cary Clark88ba9712018-04-12 14:00:24 -0400449
caryclark@google.comf68154a2012-11-21 15:18:06 +0000450bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400451 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400453 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400454 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400455 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
456 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400457 SkPoint firstCorner;
458 SkPoint thirdCorner;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000459 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400460 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400461 lineStart.set(0, 0);
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400462 signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000463 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700465 bool insertClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000466 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000467 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700468 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
469 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000470 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000471 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700473 insertClose = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000474 case kLine_Verb: {
Cary Clarka7651562018-04-17 09:30:14 -0400475 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400476 lastPt = pts;
477 }
Cary Clark88ba9712018-04-12 14:00:24 -0400478 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
479 SkVector lineDelta = lineEnd - lineStart;
480 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000481 return false; // diagonal
482 }
Cary Clark1eece782018-04-20 10:14:41 -0400483 if (!lineDelta.isFinite()) {
484 return false; // path contains infinity or NaN
485 }
Cary Clark88ba9712018-04-12 14:00:24 -0400486 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000487 break; // single point on side OK
488 }
Cary Clark48c464a2018-04-16 12:06:07 -0400489 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000490 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400491 directions[0] = nextDirection;
caryclark@google.comf1316942011-07-26 19:54:45 +0000492 corners = 1;
493 closedOrMoved = false;
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400494 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000495 break;
496 }
497 if (closedOrMoved) {
498 return false; // closed followed by a line
499 }
Cary Clark48c464a2018-04-16 12:06:07 -0400500 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000501 break; // colinear with first
502 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 closedOrMoved = autoClose;
Cary Clark48c464a2018-04-16 12:06:07 -0400504 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400505 if (3 == corners && kLine_Verb == verb) {
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400506 thirdCorner = lineEnd;
Cary Clarkb120e922018-04-18 12:25:08 -0400507 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400508 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000509 break; // colinear segment
510 }
Cary Clark48c464a2018-04-16 12:06:07 -0400511 directions[corners++] = nextDirection;
512 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400513 switch (corners) {
514 case 2:
515 firstCorner = lineStart;
516 break;
517 case 3:
518 if ((directions[0] ^ directions[2]) != 2) {
519 return false;
520 }
521 thirdCorner = lineEnd;
522 break;
523 case 4:
524 if ((directions[1] ^ directions[3]) != 2) {
525 return false;
526 }
527 break;
528 default:
529 return false; // too many direction changes
caryclark@google.comf1316942011-07-26 19:54:45 +0000530 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400531 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000532 break;
533 }
534 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000535 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000536 case kCubic_Verb:
537 return false; // quadratic, cubic not allowed
538 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400539 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700540 insertClose = true;
541 *currVerb -= 1; // try move again afterwards
542 goto addMissingClose;
543 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400544 if (!corners) {
Cary Clark8540e112018-04-11 14:30:27 -0400545 firstPt = pts;
Cary Clark8540e112018-04-11 14:30:27 -0400546 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400547 closeXY = *firstPt - *lastPt;
548 if (closeXY.fX && closeXY.fY) {
549 return false; // we're diagonal, abort
550 }
Cary Clark8540e112018-04-11 14:30:27 -0400551 }
Cary Clark88ba9712018-04-12 14:00:24 -0400552 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000553 closedOrMoved = true;
554 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000555 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000556 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000557 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700560addMissingClose:
561 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000562 }
563 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400564 if (corners < 3 || corners > 4) {
565 return false;
566 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000567 if (savePts) {
568 *ptsPtr = savePts;
569 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400570 // check if close generates diagonal
571 closeXY = *firstPt - *lastPt;
572 if (closeXY.fX && closeXY.fY) {
573 return false;
Cary Clark5c715182018-04-09 16:07:11 -0400574 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400575 if (rect) {
576 rect->set(firstCorner, thirdCorner);
577 }
578 if (isClosed) {
caryclark@google.comf68154a2012-11-21 15:18:06 +0000579 *isClosed = autoClose;
580 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400581 if (direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400582 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000583 }
Cary Clarkdbc59ba2018-04-19 07:37:29 -0400584 return true;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000585}
586
robertphillips4f662e62014-12-29 14:06:51 -0800587bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 SkDEBUGCODE(this->validate();)
589 int currVerb = 0;
590 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400591 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000592}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000593
caryclark95bc5f32015-04-08 08:34:15 -0700594bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000595 SkDEBUGCODE(this->validate();)
596 int currVerb = 0;
597 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000598 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400599 SkRect testRects[2];
600 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000601 return false;
602 }
Cary Clark5c715182018-04-09 16:07:11 -0400603 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000604 if (testRects[0].contains(testRects[1])) {
605 if (rects) {
606 rects[0] = testRects[0];
607 rects[1] = testRects[1];
608 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000609 if (dirs) {
610 dirs[0] = testDirs[0];
611 dirs[1] = testDirs[1];
612 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000613 return true;
614 }
615 if (testRects[1].contains(testRects[0])) {
616 if (rects) {
617 rects[0] = testRects[1];
618 rects[1] = testRects[0];
619 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000620 if (dirs) {
621 dirs[0] = testDirs[1];
622 dirs[1] = testDirs[0];
623 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000624 return true;
625 }
626 }
627 return false;
628}
629
Mike Reed0c3137c2018-02-20 13:57:05 -0500630bool SkPath::isOval(SkRect* bounds) const {
631 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
632}
633
634bool SkPath::isRRect(SkRRect* rrect) const {
635 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
636}
637
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638int SkPath::countPoints() const {
639 return fPathRef->countPoints();
640}
641
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000642int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643 SkDEBUGCODE(this->validate();)
644
645 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000646 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000647 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800648 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650}
651
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000652SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
654 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000655 }
656 return SkPoint::Make(0, 0);
657}
658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000659int SkPath::countVerbs() const {
660 return fPathRef->countVerbs();
661}
662
663static inline void copy_verbs_reverse(uint8_t* inorderDst,
664 const uint8_t* reversedSrc,
665 int count) {
666 for (int i = 0; i < count; ++i) {
667 inorderDst[i] = reversedSrc[~i];
668 }
669}
670
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000671int SkPath::getVerbs(uint8_t dst[], int max) const {
672 SkDEBUGCODE(this->validate();)
673
674 SkASSERT(max >= 0);
675 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000676 int count = SkMin32(max, fPathRef->countVerbs());
677 copy_verbs_reverse(dst, fPathRef->verbs(), count);
678 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000679}
680
reed@google.com294dd7b2011-10-11 11:58:32 +0000681bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000682 SkDEBUGCODE(this->validate();)
683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000684 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000685 if (count > 0) {
686 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000687 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000689 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000691 if (lastPt) {
692 lastPt->set(0, 0);
693 }
694 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695}
696
caryclarkaec25102015-04-29 08:28:30 -0700697void SkPath::setPt(int index, SkScalar x, SkScalar y) {
698 SkDEBUGCODE(this->validate();)
699
700 int count = fPathRef->countPoints();
701 if (count <= index) {
702 return;
703 } else {
704 SkPathRef::Editor ed(&fPathRef);
705 ed.atPoint(index)->set(x, y);
706 }
707}
708
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709void SkPath::setLastPt(SkScalar x, SkScalar y) {
710 SkDEBUGCODE(this->validate();)
711
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000712 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 if (count == 0) {
714 this->moveTo(x, y);
715 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000716 SkPathRef::Editor ed(&fPathRef);
717 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718 }
719}
720
reed@google.com04863fa2011-05-15 04:08:24 +0000721void SkPath::setConvexity(Convexity c) {
722 if (fConvexity != c) {
723 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000724 }
725}
726
reed@android.com8a1c16f2008-12-17 15:59:43 +0000727//////////////////////////////////////////////////////////////////////////////
728// Construction methods
729
reed026beb52015-06-10 14:23:15 -0700730#define DIRTY_AFTER_EDIT \
731 do { \
732 fConvexity = kUnknown_Convexity; \
733 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000734 } while (0)
735
reed@android.com8a1c16f2008-12-17 15:59:43 +0000736void SkPath::incReserve(U16CPU inc) {
737 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000738 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000739 SkDEBUGCODE(this->validate();)
740}
741
742void SkPath::moveTo(SkScalar x, SkScalar y) {
743 SkDEBUGCODE(this->validate();)
744
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000745 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000746
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000747 // remember our index
748 fLastMoveToIndex = fPathRef->countPoints();
749
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700751
752 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753}
754
755void SkPath::rMoveTo(SkScalar x, SkScalar y) {
756 SkPoint pt;
757 this->getLastPt(&pt);
758 this->moveTo(pt.fX + x, pt.fY + y);
759}
760
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000761void SkPath::injectMoveToIfNeeded() {
762 if (fLastMoveToIndex < 0) {
763 SkScalar x, y;
764 if (fPathRef->countVerbs() == 0) {
765 x = y = 0;
766 } else {
767 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
768 x = pt.fX;
769 y = pt.fY;
770 }
771 this->moveTo(x, y);
772 }
773}
774
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775void SkPath::lineTo(SkScalar x, SkScalar y) {
776 SkDEBUGCODE(this->validate();)
777
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000778 this->injectMoveToIfNeeded();
779
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000780 SkPathRef::Editor ed(&fPathRef);
781 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782
reed@google.comb54455e2011-05-16 14:16:04 +0000783 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000784}
785
786void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000787 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788 SkPoint pt;
789 this->getLastPt(&pt);
790 this->lineTo(pt.fX + x, pt.fY + y);
791}
792
793void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
794 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000795
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000796 this->injectMoveToIfNeeded();
797
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 SkPathRef::Editor ed(&fPathRef);
799 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 pts[0].set(x1, y1);
801 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000802
reed@google.comb54455e2011-05-16 14:16:04 +0000803 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804}
805
806void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000807 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808 SkPoint pt;
809 this->getLastPt(&pt);
810 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
811}
812
reed@google.com277c3f82013-05-31 15:17:50 +0000813void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
814 SkScalar w) {
815 // check for <= 0 or NaN with this test
816 if (!(w > 0)) {
817 this->lineTo(x2, y2);
818 } else if (!SkScalarIsFinite(w)) {
819 this->lineTo(x1, y1);
820 this->lineTo(x2, y2);
821 } else if (SK_Scalar1 == w) {
822 this->quadTo(x1, y1, x2, y2);
823 } else {
824 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000825
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000826 this->injectMoveToIfNeeded();
827
reed@google.com277c3f82013-05-31 15:17:50 +0000828 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000829 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000830 pts[0].set(x1, y1);
831 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000832
reed@google.com277c3f82013-05-31 15:17:50 +0000833 DIRTY_AFTER_EDIT;
834 }
835}
836
837void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
838 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000839 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000840 SkPoint pt;
841 this->getLastPt(&pt);
842 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
843}
844
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
846 SkScalar x3, SkScalar y3) {
847 SkDEBUGCODE(this->validate();)
848
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000849 this->injectMoveToIfNeeded();
850
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 SkPathRef::Editor ed(&fPathRef);
852 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 pts[0].set(x1, y1);
854 pts[1].set(x2, y2);
855 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856
reed@google.comb54455e2011-05-16 14:16:04 +0000857 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858}
859
860void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
861 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000862 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000863 SkPoint pt;
864 this->getLastPt(&pt);
865 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
866 pt.fX + x3, pt.fY + y3);
867}
868
869void SkPath::close() {
870 SkDEBUGCODE(this->validate();)
871
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000872 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875 case kLine_Verb:
876 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000877 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000879 case kMove_Verb: {
880 SkPathRef::Editor ed(&fPathRef);
881 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 }
reed@google.com277c3f82013-05-31 15:17:50 +0000884 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000885 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000886 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000887 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000888 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000889 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 }
891 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000892
893 // signal that we need a moveTo to follow us (unless we're done)
894#if 0
895 if (fLastMoveToIndex >= 0) {
896 fLastMoveToIndex = ~fLastMoveToIndex;
897 }
898#else
899 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
900#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000901}
902
903///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000904
fmalitac08d53e2015-11-17 09:53:29 -0800905namespace {
906
907template <unsigned N>
908class PointIterator {
909public:
910 PointIterator(SkPath::Direction dir, unsigned startIndex)
911 : fCurrent(startIndex % N)
912 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
913
914 const SkPoint& current() const {
915 SkASSERT(fCurrent < N);
916 return fPts[fCurrent];
917 }
918
919 const SkPoint& next() {
920 fCurrent = (fCurrent + fAdvance) % N;
921 return this->current();
922 }
923
924protected:
925 SkPoint fPts[N];
926
927private:
928 unsigned fCurrent;
929 unsigned fAdvance;
930};
931
932class RectPointIterator : public PointIterator<4> {
933public:
934 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
935 : PointIterator(dir, startIndex) {
936
937 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
938 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
939 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
940 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
941 }
942};
943
944class OvalPointIterator : public PointIterator<4> {
945public:
946 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
947 : PointIterator(dir, startIndex) {
948
949 const SkScalar cx = oval.centerX();
950 const SkScalar cy = oval.centerY();
951
952 fPts[0] = SkPoint::Make(cx, oval.fTop);
953 fPts[1] = SkPoint::Make(oval.fRight, cy);
954 fPts[2] = SkPoint::Make(cx, oval.fBottom);
955 fPts[3] = SkPoint::Make(oval.fLeft, cy);
956 }
957};
958
959class RRectPointIterator : public PointIterator<8> {
960public:
961 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
962 : PointIterator(dir, startIndex) {
963
964 const SkRect& bounds = rrect.getBounds();
965 const SkScalar L = bounds.fLeft;
966 const SkScalar T = bounds.fTop;
967 const SkScalar R = bounds.fRight;
968 const SkScalar B = bounds.fBottom;
969
970 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
971 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
972 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
973 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
974 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
975 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
976 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
977 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
978 }
979};
980
981} // anonymous namespace
982
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000983static void assert_known_direction(int dir) {
984 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
985}
986
reed@android.com8a1c16f2008-12-17 15:59:43 +0000987void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800988 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989}
990
991void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
992 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800993 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
994}
995
996void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000997 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -0700998 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -0800999 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001000 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001001 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001002
fmalitac08d53e2015-11-17 09:53:29 -08001003 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004
fmalitac08d53e2015-11-17 09:53:29 -08001005 const int kVerbs = 5; // moveTo + 3x lineTo + close
1006 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007
fmalitac08d53e2015-11-17 09:53:29 -08001008 RectPointIterator iter(rect, dir, startIndex);
1009
1010 this->moveTo(iter.current());
1011 this->lineTo(iter.next());
1012 this->lineTo(iter.next());
1013 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001014 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001015
1016 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001017}
1018
reed@google.com744faba2012-05-29 19:54:52 +00001019void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1020 SkDEBUGCODE(this->validate();)
1021 if (count <= 0) {
1022 return;
1023 }
1024
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001025 fLastMoveToIndex = fPathRef->countPoints();
1026
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001027 // +close makes room for the extra kClose_Verb
1028 SkPathRef::Editor ed(&fPathRef, count+close, count);
1029
1030 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001031 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001032 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1033 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001034 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001035
reed@google.com744faba2012-05-29 19:54:52 +00001036 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001037 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001038 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001039 }
1040
reed@google.com744faba2012-05-29 19:54:52 +00001041 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001042 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001043}
1044
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045#include "SkGeometry.h"
1046
reedf90ea012015-01-29 12:03:58 -08001047static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1048 SkPoint* pt) {
1049 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001050 // Chrome uses this path to move into and out of ovals. If not
1051 // treated as a special case the moves can distort the oval's
1052 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001053 pt->set(oval.fRight, oval.centerY());
1054 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001055 } else if (0 == oval.width() && 0 == oval.height()) {
1056 // Chrome will sometimes create 0 radius round rects. Having degenerate
1057 // quad segments in the path prevents the path from being recognized as
1058 // a rect.
1059 // TODO: optimizing the case where only one of width or height is zero
1060 // should also be considered. This case, however, doesn't seem to be
1061 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001062 pt->set(oval.fRight, oval.fTop);
1063 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001064 }
reedf90ea012015-01-29 12:03:58 -08001065 return false;
1066}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001067
reedd5d27d92015-02-09 13:54:43 -08001068// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1069//
1070static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1071 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1072 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1073 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001074
1075 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001076 loss in radians-conversion and/or sin/cos, we may end up with coincident
1077 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1078 of drawing a nearly complete circle (good).
1079 e.g. canvas.drawArc(0, 359.99, ...)
1080 -vs- canvas.drawArc(0, 359.9, ...)
1081 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001082 */
reedd5d27d92015-02-09 13:54:43 -08001083 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001084 SkScalar sw = SkScalarAbs(sweepAngle);
1085 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1086 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1087 // make a guess at a tiny angle (in radians) to tweak by
1088 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1089 // not sure how much will be enough, so we use a loop
1090 do {
1091 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001092 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1093 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001094 }
1095 }
reedd5d27d92015-02-09 13:54:43 -08001096 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1097}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001098
reed9e779d42015-02-17 11:43:14 -08001099/**
1100 * If this returns 0, then the caller should just line-to the singlePt, else it should
1101 * ignore singlePt and append the specified number of conics.
1102 */
reedd5d27d92015-02-09 13:54:43 -08001103static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001104 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1105 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001106 SkMatrix matrix;
1107
1108 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1109 matrix.postTranslate(oval.centerX(), oval.centerY());
1110
reed9e779d42015-02-17 11:43:14 -08001111 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1112 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001113 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001114 }
1115 return count;
reedd5d27d92015-02-09 13:54:43 -08001116}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001117
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001118void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001119 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001120 SkRRect rrect;
1121 rrect.setRectRadii(rect, (const SkVector*) radii);
1122 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123}
1124
reed@google.com4ed0fb72012-12-12 20:48:18 +00001125void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001126 // legacy start indices: 6 (CW) and 7(CCW)
1127 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1128}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001129
fmalitac08d53e2015-11-17 09:53:29 -08001130void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1131 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001132
caryclarkda707bf2015-11-19 14:47:43 -08001133 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001134 const SkRect& bounds = rrect.getBounds();
1135
Brian Salomon0a241ce2017-12-15 11:31:05 -05001136 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001137 // degenerate(rect) => radii points are collapsing
1138 this->addRect(bounds, dir, (startIndex + 1) / 2);
1139 } else if (rrect.isOval()) {
1140 // degenerate(oval) => line points are collapsing
1141 this->addOval(bounds, dir, startIndex / 2);
1142 } else {
1143 fFirstDirection = this->hasOnlyMoveTos() ?
1144 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1145
1146 SkAutoPathBoundsUpdate apbu(this, bounds);
1147 SkAutoDisableDirectionCheck addc(this);
1148
1149 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1150 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1151 const SkScalar weight = SK_ScalarRoot2Over2;
1152
1153 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1154 const int kVerbs = startsWithConic
1155 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1156 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1157 this->incReserve(kVerbs);
1158
1159 RRectPointIterator rrectIter(rrect, dir, startIndex);
1160 // Corner iterator indices follow the collapsed radii model,
1161 // adjusted such that the start pt is "behind" the radii start pt.
1162 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1163 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1164
1165 this->moveTo(rrectIter.current());
1166 if (startsWithConic) {
1167 for (unsigned i = 0; i < 3; ++i) {
1168 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1169 this->lineTo(rrectIter.next());
1170 }
1171 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1172 // final lineTo handled by close().
1173 } else {
1174 for (unsigned i = 0; i < 4; ++i) {
1175 this->lineTo(rrectIter.next());
1176 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1177 }
1178 }
1179 this->close();
1180
caryclarkda707bf2015-11-19 14:47:43 -08001181 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001182 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001183
fmalitac08d53e2015-11-17 09:53:29 -08001184 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1185 }
1186
1187 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001188}
1189
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001190bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001191 int count = fPathRef->countVerbs();
1192 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1193 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194 if (*verbs == kLine_Verb ||
1195 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001196 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001197 *verbs == kCubic_Verb) {
1198 return false;
1199 }
1200 ++verbs;
1201 }
1202 return true;
1203}
1204
Brian Osmana2318572017-07-10 15:09:26 -04001205bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1206 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001207 if (count < 2) {
1208 return true;
1209 }
Brian Osmana2318572017-07-10 15:09:26 -04001210 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001211 const SkPoint& first = *pts;
1212 for (int index = 1; index < count; ++index) {
1213 if (first != pts[index]) {
1214 return false;
1215 }
1216 }
1217 return true;
1218}
1219
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001220void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1221 Direction dir) {
1222 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001223
humper@google.com75e3ca12013-04-08 21:44:11 +00001224 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001225 return;
1226 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001227
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001228 SkRRect rrect;
1229 rrect.setRectXY(rect, rx, ry);
1230 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001231}
1232
reed@android.com8a1c16f2008-12-17 15:59:43 +00001233void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001234 // legacy start index: 1
1235 this->addOval(oval, dir, 1);
1236}
1237
1238void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001239 assert_known_direction(dir);
1240
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001241 /* If addOval() is called after previous moveTo(),
1242 this path is still marked as an oval. This is used to
1243 fit into WebKit's calling sequences.
1244 We can't simply check isEmpty() in this case, as additional
1245 moveTo() would mark the path non empty.
1246 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001247 bool isOval = hasOnlyMoveTos();
1248 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001249 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001250 } else {
reed026beb52015-06-10 14:23:15 -07001251 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001252 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001253
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001254 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001255 SkAutoPathBoundsUpdate apbu(this, oval);
1256
fmalitac08d53e2015-11-17 09:53:29 -08001257 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1258 const int kVerbs = 6; // moveTo + 4x conicTo + close
1259 this->incReserve(kVerbs);
1260
1261 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1262 // The corner iterator pts are tracking "behind" the oval/radii pts.
1263 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001264 const SkScalar weight = SK_ScalarRoot2Over2;
1265
fmalitac08d53e2015-11-17 09:53:29 -08001266 this->moveTo(ovalIter.current());
1267 for (unsigned i = 0; i < 4; ++i) {
1268 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001269 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001271
fmalitac08d53e2015-11-17 09:53:29 -08001272 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1273
robertphillips@google.com466310d2013-12-03 16:43:54 +00001274 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001275
bsalomon78d58d12016-05-27 09:17:04 -07001276 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001277}
1278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1280 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001281 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 }
1283}
1284
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1286 bool forceMoveTo) {
1287 if (oval.width() < 0 || oval.height() < 0) {
1288 return;
1289 }
1290
reedf90ea012015-01-29 12:03:58 -08001291 if (fPathRef->countVerbs() == 0) {
1292 forceMoveTo = true;
1293 }
1294
1295 SkPoint lonePt;
1296 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1297 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1298 return;
1299 }
1300
reedd5d27d92015-02-09 13:54:43 -08001301 SkVector startV, stopV;
1302 SkRotationDirection dir;
1303 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1304
reed9e779d42015-02-17 11:43:14 -08001305 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001306
1307 // At this point, we know that the arc is not a lone point, but startV == stopV
1308 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1309 // cannot handle it.
1310 if (startV == stopV) {
1311 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1312 SkScalar radiusX = oval.width() / 2;
1313 SkScalar radiusY = oval.height() / 2;
1314 // We cannot use SkScalarSinCos function in the next line because
1315 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1316 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001317 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001318 // make sin(endAngle) to be 0 which will then draw a dot.
1319 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1320 oval.centerY() + radiusY * sk_float_sin(endAngle));
1321 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1322 return;
1323 }
1324
reedd5d27d92015-02-09 13:54:43 -08001325 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001326 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001327 if (count) {
1328 this->incReserve(count * 2 + 1);
1329 const SkPoint& pt = conics[0].fPts[0];
1330 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1331 for (int i = 0; i < count; ++i) {
1332 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1333 }
reed9e779d42015-02-17 11:43:14 -08001334 } else {
1335 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001336 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337}
1338
caryclark55d49052016-01-23 05:07:04 -08001339// This converts the SVG arc to conics.
1340// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1341// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1342// See also SVG implementation notes:
1343// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1344// Note that arcSweep bool value is flipped from the original implementation.
1345void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1346 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001347 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001348 SkPoint srcPts[2];
1349 this->getLastPt(&srcPts[0]);
1350 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1351 // joining the endpoints.
1352 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1353 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001354 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001355 return;
1356 }
1357 // If the current point and target point for the arc are identical, it should be treated as a
1358 // zero length path. This ensures continuity in animations.
1359 srcPts[1].set(x, y);
1360 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001361 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001362 return;
1363 }
1364 rx = SkScalarAbs(rx);
1365 ry = SkScalarAbs(ry);
1366 SkVector midPointDistance = srcPts[0] - srcPts[1];
1367 midPointDistance *= 0.5f;
1368
1369 SkMatrix pointTransform;
1370 pointTransform.setRotate(-angle);
1371
1372 SkPoint transformedMidPoint;
1373 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1374 SkScalar squareRx = rx * rx;
1375 SkScalar squareRy = ry * ry;
1376 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1377 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1378
1379 // Check if the radii are big enough to draw the arc, scale radii if not.
1380 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1381 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1382 if (radiiScale > 1) {
1383 radiiScale = SkScalarSqrt(radiiScale);
1384 rx *= radiiScale;
1385 ry *= radiiScale;
1386 }
1387
1388 pointTransform.setScale(1 / rx, 1 / ry);
1389 pointTransform.preRotate(-angle);
1390
1391 SkPoint unitPts[2];
1392 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1393 SkVector delta = unitPts[1] - unitPts[0];
1394
1395 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1396 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1397
1398 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1399 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1400 scaleFactor = -scaleFactor;
1401 }
1402 delta.scale(scaleFactor);
1403 SkPoint centerPoint = unitPts[0] + unitPts[1];
1404 centerPoint *= 0.5f;
1405 centerPoint.offset(-delta.fY, delta.fX);
1406 unitPts[0] -= centerPoint;
1407 unitPts[1] -= centerPoint;
1408 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1409 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1410 SkScalar thetaArc = theta2 - theta1;
1411 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1412 thetaArc += SK_ScalarPI * 2;
1413 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1414 thetaArc -= SK_ScalarPI * 2;
1415 }
1416 pointTransform.setRotate(angle);
1417 pointTransform.preScale(rx, ry);
1418
Cary Clark1acd3c72017-12-08 11:37:01 -05001419#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001420 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001421#else
1422 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1423 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1424#endif
caryclark55d49052016-01-23 05:07:04 -08001425 SkScalar thetaWidth = thetaArc / segments;
1426 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1427 if (!SkScalarIsFinite(t)) {
1428 return;
1429 }
1430 SkScalar startTheta = theta1;
1431 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001432#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1433 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1434 return scalar == SkScalarFloorToScalar(scalar);
1435 };
1436 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1437 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1438 scalar_is_integer(x) && scalar_is_integer(y);
1439#endif
caryclark55d49052016-01-23 05:07:04 -08001440 for (int i = 0; i < segments; ++i) {
1441 SkScalar endTheta = startTheta + thetaWidth;
1442 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1443
1444 unitPts[1].set(cosEndTheta, sinEndTheta);
1445 unitPts[1] += centerPoint;
1446 unitPts[0] = unitPts[1];
1447 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1448 SkPoint mapped[2];
1449 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001450 /*
1451 Computing the arc width introduces rounding errors that cause arcs to start
1452 outside their marks. A round rect may lose convexity as a result. If the input
1453 values are on integers, place the conic on integers as well.
1454 */
1455#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1456 if (expectIntegers) {
1457 SkScalar* mappedScalars = &mapped[0].fX;
1458 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1459 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1460 }
1461 }
1462#endif
caryclark55d49052016-01-23 05:07:04 -08001463 this->conicTo(mapped[0], mapped[1], w);
1464 startTheta = endTheta;
1465 }
1466}
1467
1468void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1469 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1470 SkPoint currentPoint;
1471 this->getLastPt(&currentPoint);
1472 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1473}
1474
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001475void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 if (oval.isEmpty() || 0 == sweepAngle) {
1477 return;
1478 }
1479
1480 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1481
1482 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001483 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1484 // See SkPath::addOval() docs.
1485 SkScalar startOver90 = startAngle / 90.f;
1486 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1487 SkScalar error = startOver90 - startOver90I;
1488 if (SkScalarNearlyEqual(error, 0)) {
1489 // Index 1 is at startAngle == 0.
1490 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1491 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1492 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1493 (unsigned) startIndex);
1494 return;
1495 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 }
bsalomon1978ce22016-05-31 10:42:16 -07001497 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498}
1499
1500/*
1501 Need to handle the case when the angle is sharp, and our computed end-points
1502 for the arc go behind pt1 and/or p2...
1503*/
reedc7789042015-01-29 12:59:11 -08001504void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001505 if (radius == 0) {
1506 this->lineTo(x1, y1);
1507 return;
1508 }
1509
1510 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001511
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512 // need to know our prev pt so we can construct tangent vectors
1513 {
1514 SkPoint start;
1515 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001516 // Handle degenerate cases by adding a line to the first point and
1517 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 before.setNormalize(x1 - start.fX, y1 - start.fY);
1519 after.setNormalize(x2 - x1, y2 - y1);
1520 }
reed@google.comabf15c12011-01-18 20:35:51 +00001521
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 SkScalar cosh = SkPoint::DotProduct(before, after);
1523 SkScalar sinh = SkPoint::CrossProduct(before, after);
1524
1525 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001526 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 return;
1528 }
reed@google.comabf15c12011-01-18 20:35:51 +00001529
Mike Reeda99b6ce2017-02-04 11:04:26 -05001530 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531
Mike Reeda99b6ce2017-02-04 11:04:26 -05001532 SkScalar xx = x1 - dist * before.fX;
1533 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001534 after.setLength(dist);
1535 this->lineTo(xx, yy);
1536 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1537 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001538}
1539
1540///////////////////////////////////////////////////////////////////////////////
1541
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001542void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001543 SkMatrix matrix;
1544
1545 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001546 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547}
1548
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001549void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001550 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001552 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553 SkPoint pts[4];
1554 Verb verb;
1555
Cary Clark9480d822017-10-19 18:01:13 -04001556 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001557 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 while ((verb = iter.next(pts)) != kDone_Verb) {
1559 switch (verb) {
1560 case kMove_Verb:
1561 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001562 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1563 injectMoveToIfNeeded(); // In case last contour is closed
1564 this->lineTo(pts[0]);
1565 } else {
1566 this->moveTo(pts[0]);
1567 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 break;
1569 case kLine_Verb:
1570 proc(matrix, &pts[1], &pts[1], 1);
1571 this->lineTo(pts[1]);
1572 break;
1573 case kQuad_Verb:
1574 proc(matrix, &pts[1], &pts[1], 2);
1575 this->quadTo(pts[1], pts[2]);
1576 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001577 case kConic_Verb:
1578 proc(matrix, &pts[1], &pts[1], 2);
1579 this->conicTo(pts[1], pts[2], iter.conicWeight());
1580 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001581 case kCubic_Verb:
1582 proc(matrix, &pts[1], &pts[1], 3);
1583 this->cubicTo(pts[1], pts[2], pts[3]);
1584 break;
1585 case kClose_Verb:
1586 this->close();
1587 break;
1588 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001589 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001590 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001591 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592 }
1593}
1594
1595///////////////////////////////////////////////////////////////////////////////
1596
reed@google.com277c3f82013-05-31 15:17:50 +00001597static int pts_in_verb(unsigned verb) {
1598 static const uint8_t gPtsInVerb[] = {
1599 1, // kMove
1600 1, // kLine
1601 2, // kQuad
1602 2, // kConic
1603 3, // kCubic
1604 0, // kClose
1605 0 // kDone
1606 };
1607
1608 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1609 return gPtsInVerb[verb];
1610}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611
reed@android.com8a1c16f2008-12-17 15:59:43 +00001612// ignore the last point of the 1st contour
1613void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001614 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1615 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001616 return;
1617 }
caryclark51c56782016-11-07 05:09:28 -08001618 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1619 SkASSERT(verbsEnd[0] == kMove_Verb);
1620 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1621 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001622
caryclark51c56782016-11-07 05:09:28 -08001623 while (verbs < verbsEnd) {
1624 uint8_t v = *verbs++;
1625 pts -= pts_in_verb(v);
1626 switch (v) {
1627 case kMove_Verb:
1628 // if the path has multiple contours, stop after reversing the last
1629 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001630 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001631 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 break;
1633 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001634 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001636 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001637 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001638 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001640 this->cubicTo(pts[2], pts[1], pts[0]);
1641 break;
1642 case kClose_Verb:
1643 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 break;
1645 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001646 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 break;
1648 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001649 }
1650}
1651
reed@google.com63d73742012-01-10 15:33:12 +00001652void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001653 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001654
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001655 const SkPoint* pts = src.fPathRef->pointsEnd();
1656 // we will iterator through src's verbs backwards
1657 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1658 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001659 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001660
1661 bool needMove = true;
1662 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001663 while (verbs < verbsEnd) {
1664 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001665 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001666
1667 if (needMove) {
1668 --pts;
1669 this->moveTo(pts->fX, pts->fY);
1670 needMove = false;
1671 }
1672 pts -= n;
1673 switch (v) {
1674 case kMove_Verb:
1675 if (needClose) {
1676 this->close();
1677 needClose = false;
1678 }
1679 needMove = true;
1680 pts += 1; // so we see the point in "if (needMove)" above
1681 break;
1682 case kLine_Verb:
1683 this->lineTo(pts[0]);
1684 break;
1685 case kQuad_Verb:
1686 this->quadTo(pts[1], pts[0]);
1687 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001688 case kConic_Verb:
1689 this->conicTo(pts[1], pts[0], *--conicWeights);
1690 break;
reed@google.com63d73742012-01-10 15:33:12 +00001691 case kCubic_Verb:
1692 this->cubicTo(pts[2], pts[1], pts[0]);
1693 break;
1694 case kClose_Verb:
1695 needClose = true;
1696 break;
1697 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001698 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001699 }
1700 }
1701}
1702
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703///////////////////////////////////////////////////////////////////////////////
1704
1705void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1706 SkMatrix matrix;
1707
1708 matrix.setTranslate(dx, dy);
1709 this->transform(matrix, dst);
1710}
1711
reed@android.com8a1c16f2008-12-17 15:59:43 +00001712static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1713 int level = 2) {
1714 if (--level >= 0) {
1715 SkPoint tmp[7];
1716
1717 SkChopCubicAtHalf(pts, tmp);
1718 subdivide_cubic_to(path, &tmp[0], level);
1719 subdivide_cubic_to(path, &tmp[3], level);
1720 } else {
1721 path->cubicTo(pts[1], pts[2], pts[3]);
1722 }
1723}
1724
1725void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1726 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001727 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 dst = (SkPath*)this;
1729 }
1730
tomhudson@google.com8d430182011-06-06 19:11:19 +00001731 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 SkPath tmp;
1733 tmp.fFillType = fFillType;
1734
1735 SkPath::Iter iter(*this, false);
1736 SkPoint pts[4];
1737 SkPath::Verb verb;
1738
reed@google.com4a3b7142012-05-16 17:16:46 +00001739 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 switch (verb) {
1741 case kMove_Verb:
1742 tmp.moveTo(pts[0]);
1743 break;
1744 case kLine_Verb:
1745 tmp.lineTo(pts[1]);
1746 break;
1747 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001748 // promote the quad to a conic
1749 tmp.conicTo(pts[1], pts[2],
1750 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001751 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001752 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001753 tmp.conicTo(pts[1], pts[2],
1754 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001755 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 case kCubic_Verb:
1757 subdivide_cubic_to(&tmp, pts);
1758 break;
1759 case kClose_Verb:
1760 tmp.close();
1761 break;
1762 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001763 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764 break;
1765 }
1766 }
1767
1768 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001769 SkPathRef::Editor ed(&dst->fPathRef);
1770 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001771 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001772 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1774
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001777 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001778 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001780
reed026beb52015-06-10 14:23:15 -07001781 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1782 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001783 } else {
1784 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001785 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1786 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001787 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001788 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1789 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001790 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001791 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001792 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001793 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001794 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001795 }
1796 }
1797
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 SkDEBUGCODE(dst->validate();)
1799 }
1800}
1801
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802///////////////////////////////////////////////////////////////////////////////
1803///////////////////////////////////////////////////////////////////////////////
1804
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001805enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001806 kEmptyContour_SegmentState, // The current contour is empty. We may be
1807 // starting processing or we may have just
1808 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1810 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1811 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812};
1813
1814SkPath::Iter::Iter() {
1815#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001816 fPts = nullptr;
1817 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001820 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001821#endif
1822 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001823 fVerbs = nullptr;
1824 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825 fNeedClose = false;
1826}
1827
1828SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1829 this->setPath(path, forceClose);
1830}
1831
1832void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001833 fPts = path.fPathRef->points();
1834 fVerbs = path.fPathRef->verbs();
1835 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001836 fConicWeights = path.fPathRef->conicWeights();
1837 if (fConicWeights) {
1838 fConicWeights -= 1; // begin one behind
1839 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001840 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001841 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842 fForceClose = SkToU8(forceClose);
1843 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001844 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001845}
1846
1847bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001848 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849 return false;
1850 }
1851 if (fForceClose) {
1852 return true;
1853 }
1854
1855 const uint8_t* verbs = fVerbs;
1856 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001857
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001858 if (kMove_Verb == *(verbs - 1)) {
1859 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860 }
1861
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001862 while (verbs > stop) {
1863 // verbs points one beyond the current verb, decrement first.
1864 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001865 if (kMove_Verb == v) {
1866 break;
1867 }
1868 if (kClose_Verb == v) {
1869 return true;
1870 }
1871 }
1872 return false;
1873}
1874
1875SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001876 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001878 // A special case: if both points are NaN, SkPoint::operation== returns
1879 // false, but the iterator expects that they are treated as the same.
1880 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001881 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1882 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001883 return kClose_Verb;
1884 }
1885
reed@google.com9e25dbf2012-05-15 17:05:38 +00001886 pts[0] = fLastPt;
1887 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 fLastPt = fMoveTo;
1889 fCloseLine = true;
1890 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001891 } else {
1892 pts[0] = fMoveTo;
1893 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001895}
1896
reed@google.com9e25dbf2012-05-15 17:05:38 +00001897const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 if (fSegmentState == kAfterMove_SegmentState) {
1899 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1904 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001905 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907}
1908
caryclarke8c56662015-07-14 11:19:26 -07001909void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 // We need to step over anything that will not move the current draw point
1911 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001912 const uint8_t* lastMoveVerb = nullptr;
1913 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001914 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 SkPoint lastPt = fLastPt;
1916 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001917 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 switch (verb) {
1919 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 // Keep a record of this most recent move
1921 lastMoveVerb = fVerbs;
1922 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001923 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001924 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001925 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 fPts++;
1927 break;
1928
1929 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001930 // A close when we are in a segment is always valid except when it
1931 // follows a move which follows a segment.
1932 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 return;
1934 }
1935 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001936 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 break;
1938
1939 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001940 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 if (lastMoveVerb) {
1942 fVerbs = lastMoveVerb;
1943 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001944 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 return;
1946 }
1947 return;
1948 }
1949 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001950 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951 fPts++;
1952 break;
1953
reed@google.com277c3f82013-05-31 15:17:50 +00001954 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001955 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001956 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001957 if (lastMoveVerb) {
1958 fVerbs = lastMoveVerb;
1959 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001960 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 return;
1962 }
1963 return;
1964 }
1965 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001966 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001967 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001968 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001969 break;
1970
1971 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001972 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001973 if (lastMoveVerb) {
1974 fVerbs = lastMoveVerb;
1975 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001976 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 return;
1978 }
1979 return;
1980 }
1981 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001982 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001983 fPts += 3;
1984 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001985
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001986 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001987 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 }
1989 }
1990}
1991
reed@google.com4a3b7142012-05-16 17:16:46 +00001992SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001993 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001994
reed@android.com8a1c16f2008-12-17 15:59:43 +00001995 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001996 // Close the curve if requested and if there is some curve to close
1997 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001998 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 return kLine_Verb;
2000 }
2001 fNeedClose = false;
2002 return kClose_Verb;
2003 }
2004 return kDone_Verb;
2005 }
2006
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002007 // fVerbs is one beyond the current verb, decrement first
2008 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002009 const SkPoint* SK_RESTRICT srcPts = fPts;
2010 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002011
2012 switch (verb) {
2013 case kMove_Verb:
2014 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002015 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016 verb = this->autoClose(pts);
2017 if (verb == kClose_Verb) {
2018 fNeedClose = false;
2019 }
2020 return (Verb)verb;
2021 }
2022 if (fVerbs == fVerbStop) { // might be a trailing moveto
2023 return kDone_Verb;
2024 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002025 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002026 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002028 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002029 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030 fNeedClose = fForceClose;
2031 break;
2032 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002033 pts[0] = this->cons_moveTo();
2034 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002035 fLastPt = srcPts[0];
2036 fCloseLine = false;
2037 srcPts += 1;
2038 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002039 case kConic_Verb:
2040 fConicWeights += 1;
2041 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002043 pts[0] = this->cons_moveTo();
2044 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045 fLastPt = srcPts[1];
2046 srcPts += 2;
2047 break;
2048 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002049 pts[0] = this->cons_moveTo();
2050 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051 fLastPt = srcPts[2];
2052 srcPts += 3;
2053 break;
2054 case kClose_Verb:
2055 verb = this->autoClose(pts);
2056 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002057 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002058 } else {
2059 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002060 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002062 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002063 break;
2064 }
2065 fPts = srcPts;
2066 return (Verb)verb;
2067}
2068
2069///////////////////////////////////////////////////////////////////////////////
2070
Ben Wagner4d1955c2017-03-10 13:08:15 -05002071#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002072#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002073#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002074
reed@google.com51bbe752013-01-17 22:07:50 +00002075static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002076 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002077 str->append(label);
2078 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002079
reed@google.com51bbe752013-01-17 22:07:50 +00002080 const SkScalar* values = &pts[0].fX;
2081 count *= 2;
2082
2083 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002084 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002085 if (i < count - 1) {
2086 str->append(", ");
2087 }
2088 }
Mike Reed405b9d22018-01-09 15:11:08 -05002089 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002090 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002091 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002092 }
caryclark08fa28c2014-10-23 13:08:56 -07002093 str->append(");");
reede05fed02014-12-15 07:59:53 -08002094 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002095 str->append(" // ");
2096 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002097 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002098 if (i < count - 1) {
2099 str->append(", ");
2100 }
2101 }
2102 if (conicWeight >= 0) {
2103 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002104 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002105 }
2106 }
2107 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002108}
2109
caryclarke9562592014-09-15 09:26:09 -07002110void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002111 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002112 Iter iter(*this, forceClose);
2113 SkPoint pts[4];
2114 Verb verb;
2115
reed@google.com51bbe752013-01-17 22:07:50 +00002116 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002117 char const * const gFillTypeStrs[] = {
2118 "Winding",
2119 "EvenOdd",
2120 "InverseWinding",
2121 "InverseEvenOdd",
2122 };
2123 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2124 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002125 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 switch (verb) {
2127 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002128 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 break;
2130 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002131 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132 break;
2133 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002134 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002136 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002137 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002138 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002140 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 break;
2142 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002143 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
2145 default:
2146 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2147 verb = kDone_Verb; // stop the loop
2148 break;
2149 }
caryclark1049f122015-04-20 08:31:59 -07002150 if (!wStream && builder.size()) {
2151 SkDebugf("%s", builder.c_str());
2152 builder.reset();
2153 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 }
caryclark66a5d8b2014-06-24 08:30:15 -07002155 if (wStream) {
2156 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002157 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158}
2159
reed@android.come522ca52009-11-23 20:10:41 +00002160void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002161 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002162}
2163
2164void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002165 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002166}
2167
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002168
Cary Clark0461e9f2017-08-25 15:13:38 -04002169bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002170 if ((fFillType & ~3) != 0) {
2171 return false;
2172 }
reed@google.comabf15c12011-01-18 20:35:51 +00002173
djsollen@google.com077348c2012-10-22 20:23:32 +00002174#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002175 if (!fBoundsIsDirty) {
2176 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002177
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002178 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002179 if (SkToBool(fIsFinite) != isFinite) {
2180 return false;
2181 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002182
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002183 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002184 // if we're empty, fBounds may be empty but translated, so we can't
2185 // necessarily compare to bounds directly
2186 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2187 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002188 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2189 return false;
2190 }
reed@android.come522ca52009-11-23 20:10:41 +00002191 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002192 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002193 if (!fBounds.isEmpty()) {
2194 return false;
2195 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002196 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002197 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002198 if (!fBounds.contains(bounds)) {
2199 return false;
2200 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002201 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002202 }
reed@android.come522ca52009-11-23 20:10:41 +00002203 }
2204 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002205#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002206 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002207}
reed@android.come522ca52009-11-23 20:10:41 +00002208
reed@google.com04863fa2011-05-15 04:08:24 +00002209///////////////////////////////////////////////////////////////////////////////
2210
reed@google.com0b7b9822011-05-16 12:29:27 +00002211static int sign(SkScalar x) { return x < 0; }
2212#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002213
robertphillipsc506e302014-09-16 09:43:31 -07002214enum DirChange {
2215 kLeft_DirChange,
2216 kRight_DirChange,
2217 kStraight_DirChange,
2218 kBackwards_DirChange,
2219
2220 kInvalid_DirChange
2221};
2222
2223
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002224static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002225 // The error epsilon was empirically derived; worse case round rects
2226 // with a mid point outset by 2x float epsilon in tests had an error
2227 // of 12.
2228 const int epsilon = 16;
2229 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2230 return false;
2231 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002232 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002233 int aBits = SkFloatAs2sCompliment(compA);
2234 int bBits = SkFloatAs2sCompliment(compB);
2235 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002236}
2237
caryclarkb4216502015-03-02 13:02:34 -08002238static bool approximately_zero_when_compared_to(double x, double y) {
2239 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002240}
2241
caryclarkb4216502015-03-02 13:02:34 -08002242
reed@google.com04863fa2011-05-15 04:08:24 +00002243// only valid for a single contour
2244struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002245 Convexicator()
2246 : fPtCount(0)
2247 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002248 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002249 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002250 , fIsCurve(false)
2251 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002252 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002253 // warnings
djsollen2f124632016-04-29 13:53:05 -07002254 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002255 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002256 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002257 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002258 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002259
2260 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002261 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002262 }
2263
2264 SkPath::Convexity getConvexity() const { return fConvexity; }
2265
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002266 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002267 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002268
reed@google.com04863fa2011-05-15 04:08:24 +00002269 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002270 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002271 return;
2272 }
2273
2274 if (0 == fPtCount) {
2275 fCurrPt = pt;
2276 ++fPtCount;
2277 } else {
2278 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002279 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002280 if (!SkScalarIsFinite(lengthSqd)) {
2281 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002282 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002283 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002284 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002285 fCurrPt = pt;
2286 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002287 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002288 } else {
2289 SkASSERT(fPtCount > 2);
2290 this->addVec(vec);
2291 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002292
reed@google.com85b6e392011-05-15 20:25:17 +00002293 int sx = sign(vec.fX);
2294 int sy = sign(vec.fY);
2295 fDx += (sx != fSx);
2296 fDy += (sy != fSy);
2297 fSx = sx;
2298 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002299
reed@google.com85b6e392011-05-15 20:25:17 +00002300 if (fDx > 3 || fDy > 3) {
2301 fConvexity = SkPath::kConcave_Convexity;
2302 }
reed@google.com04863fa2011-05-15 04:08:24 +00002303 }
2304 }
2305 }
2306
2307 void close() {
2308 if (fPtCount > 2) {
2309 this->addVec(fFirstVec);
2310 }
2311 }
2312
caryclarkb4216502015-03-02 13:02:34 -08002313 DirChange directionChange(const SkVector& curVec) {
2314 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2315
2316 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2317 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2318 largest = SkTMax(largest, -smallest);
2319
2320 if (!almost_equal(largest, largest + cross)) {
2321 int sign = SkScalarSignAsInt(cross);
2322 if (sign) {
2323 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2324 }
2325 }
2326
2327 if (cross) {
2328 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2329 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2330 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2331 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2332 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2333 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2334 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2335 if (sign) {
2336 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2337 }
2338 }
2339 }
2340
Cary Clarkdf429f32017-11-08 11:44:31 -05002341 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2342 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2343 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2344 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002345 fLastVec.dot(curVec) < 0.0f) {
2346 return kBackwards_DirChange;
2347 }
2348
2349 return kStraight_DirChange;
2350 }
2351
Cary Clarkc8323aa2017-08-25 08:04:43 -04002352 bool hasBackwards() const {
2353 return fBackwards;
2354 }
caryclarkb4216502015-03-02 13:02:34 -08002355
caryclarkd3d1a982014-12-08 04:57:38 -08002356 bool isFinite() const {
2357 return fIsFinite;
2358 }
2359
caryclark5ccef572015-03-02 10:07:56 -08002360 void setCurve(bool isCurve) {
2361 fIsCurve = isCurve;
2362 }
2363
reed@google.com04863fa2011-05-15 04:08:24 +00002364private:
2365 void addVec(const SkVector& vec) {
2366 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002367 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002368 switch (dir) {
2369 case kLeft_DirChange: // fall through
2370 case kRight_DirChange:
2371 if (kInvalid_DirChange == fExpectedDir) {
2372 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002373 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2374 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002375 } else if (dir != fExpectedDir) {
2376 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002377 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002378 }
2379 fLastVec = vec;
2380 break;
2381 case kStraight_DirChange:
2382 break;
2383 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002384 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002385 // If any of the subsequent dir is non-backward, it'll be concave.
2386 // Otherwise, it's still convex.
2387 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002388 }
robertphillipsc506e302014-09-16 09:43:31 -07002389 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002390 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002391 break;
2392 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002393 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002394 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002395 }
2396 }
2397
caryclarkb4216502015-03-02 13:02:34 -08002398 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002399 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002400 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002401 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2402 // value with the current vec is deemed to be of a significant value.
2403 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002405 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002406 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002407 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002408 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002409 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002410 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002411 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002412};
2413
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002414SkPath::Convexity SkPath::internalGetConvexity() const {
2415 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002416 SkPoint pts[4];
2417 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002418 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002419
2420 int contourCount = 0;
2421 int count;
2422 Convexicator state;
2423
caryclarkd3d1a982014-12-08 04:57:38 -08002424 if (!isFinite()) {
2425 return kUnknown_Convexity;
2426 }
Brian Osman205a1262017-09-18 13:13:48 +00002427 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002428 switch (verb) {
2429 case kMove_Verb:
2430 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002431 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002432 return kConcave_Convexity;
2433 }
2434 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002435 // fall through
2436 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002437 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002438 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002439 break;
caryclark5ccef572015-03-02 10:07:56 -08002440 case kQuad_Verb:
2441 // fall through
2442 case kConic_Verb:
2443 // fall through
2444 case kCubic_Verb:
2445 count = 2 + (kCubic_Verb == verb);
2446 // As an additional enhancement, this could set curve true only
2447 // if the curve is nonlinear
2448 state.setCurve(true);
2449 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002450 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002451 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002452 state.close();
2453 count = 0;
2454 break;
2455 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002456 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002457 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002458 return kConcave_Convexity;
2459 }
2460
2461 for (int i = 1; i <= count; i++) {
2462 state.addPt(pts[i]);
2463 }
2464 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002465 if (!state.isFinite()) {
2466 return kUnknown_Convexity;
2467 }
reed@google.com04863fa2011-05-15 04:08:24 +00002468 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002469 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002470 return kConcave_Convexity;
2471 }
2472 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002473 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002474 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002475 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2476 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2477 fConvexity = Convexity::kConcave_Convexity;
2478 } else {
2479 fFirstDirection = state.getFirstDirection();
2480 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002481 }
2482 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002483}
reed@google.com69a99432012-01-10 18:00:10 +00002484
2485///////////////////////////////////////////////////////////////////////////////
2486
2487class ContourIter {
2488public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002489 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002490
2491 bool done() const { return fDone; }
2492 // if !done() then these may be called
2493 int count() const { return fCurrPtCount; }
2494 const SkPoint* pts() const { return fCurrPt; }
2495 void next();
2496
2497private:
2498 int fCurrPtCount;
2499 const SkPoint* fCurrPt;
2500 const uint8_t* fCurrVerb;
2501 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002502 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002503 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002504 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002505};
2506
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002507ContourIter::ContourIter(const SkPathRef& pathRef) {
2508 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002509 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002510 fCurrPt = pathRef.points();
2511 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002512 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002513 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002514 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002515 this->next();
2516}
2517
2518void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002519 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002520 fDone = true;
2521 }
2522 if (fDone) {
2523 return;
2524 }
2525
2526 // skip pts of prev contour
2527 fCurrPt += fCurrPtCount;
2528
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002529 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002530 int ptCount = 1; // moveTo
2531 const uint8_t* verbs = fCurrVerb;
2532
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002533 for (--verbs; verbs > fStopVerbs; --verbs) {
2534 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002535 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002536 goto CONTOUR_END;
2537 case SkPath::kLine_Verb:
2538 ptCount += 1;
2539 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002540 case SkPath::kConic_Verb:
2541 fCurrConicWeight += 1;
2542 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002543 case SkPath::kQuad_Verb:
2544 ptCount += 2;
2545 break;
2546 case SkPath::kCubic_Verb:
2547 ptCount += 3;
2548 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002549 case SkPath::kClose_Verb:
2550 break;
2551 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002552 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002553 break;
2554 }
2555 }
2556CONTOUR_END:
2557 fCurrPtCount = ptCount;
2558 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002559 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002560}
2561
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002562// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002563static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002564 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2565 // We may get 0 when the above subtracts underflow. We expect this to be
2566 // very rare and lazily promote to double.
2567 if (0 == cross) {
2568 double p0x = SkScalarToDouble(p0.fX);
2569 double p0y = SkScalarToDouble(p0.fY);
2570
2571 double p1x = SkScalarToDouble(p1.fX);
2572 double p1y = SkScalarToDouble(p1.fY);
2573
2574 double p2x = SkScalarToDouble(p2.fX);
2575 double p2y = SkScalarToDouble(p2.fY);
2576
2577 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2578 (p1y - p0y) * (p2x - p0x));
2579
2580 }
2581 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002582}
2583
reed@google.comc1ea60a2012-01-31 15:15:36 +00002584// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002585static int find_max_y(const SkPoint pts[], int count) {
2586 SkASSERT(count > 0);
2587 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002588 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002589 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002590 SkScalar y = pts[i].fY;
2591 if (y > max) {
2592 max = y;
2593 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002594 }
2595 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002596 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002597}
2598
reed@google.comcabaf1d2012-01-11 21:03:05 +00002599static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2600 int i = index;
2601 for (;;) {
2602 i = (i + inc) % n;
2603 if (i == index) { // we wrapped around, so abort
2604 break;
2605 }
2606 if (pts[index] != pts[i]) { // found a different point, success!
2607 break;
2608 }
2609 }
2610 return i;
2611}
2612
reed@google.comc1ea60a2012-01-31 15:15:36 +00002613/**
2614 * Starting at index, and moving forward (incrementing), find the xmin and
2615 * xmax of the contiguous points that have the same Y.
2616 */
2617static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2618 int* maxIndexPtr) {
2619 const SkScalar y = pts[index].fY;
2620 SkScalar min = pts[index].fX;
2621 SkScalar max = min;
2622 int minIndex = index;
2623 int maxIndex = index;
2624 for (int i = index + 1; i < n; ++i) {
2625 if (pts[i].fY != y) {
2626 break;
2627 }
2628 SkScalar x = pts[i].fX;
2629 if (x < min) {
2630 min = x;
2631 minIndex = i;
2632 } else if (x > max) {
2633 max = x;
2634 maxIndex = i;
2635 }
2636 }
2637 *maxIndexPtr = maxIndex;
2638 return minIndex;
2639}
2640
reed026beb52015-06-10 14:23:15 -07002641static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2642 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002643}
2644
reed@google.comac8543f2012-01-30 20:51:25 +00002645/*
2646 * We loop through all contours, and keep the computed cross-product of the
2647 * contour that contained the global y-max. If we just look at the first
2648 * contour, we may find one that is wound the opposite way (correctly) since
2649 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2650 * that is outer most (or at least has the global y-max) before we can consider
2651 * its cross product.
2652 */
reed026beb52015-06-10 14:23:15 -07002653bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002654 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2655 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002656 return true;
2657 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002658
2659 // don't want to pay the cost for computing this if it
2660 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002661 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2662 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002663 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002664 return false;
2665 }
reed@google.com69a99432012-01-10 18:00:10 +00002666
reed026beb52015-06-10 14:23:15 -07002667 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002668
reed@google.comac8543f2012-01-30 20:51:25 +00002669 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002670 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002671 SkScalar ymaxCross = 0;
2672
reed@google.com69a99432012-01-10 18:00:10 +00002673 for (; !iter.done(); iter.next()) {
2674 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002675 if (n < 3) {
2676 continue;
2677 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002678
reed@google.comcabaf1d2012-01-11 21:03:05 +00002679 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002680 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002681 int index = find_max_y(pts, n);
2682 if (pts[index].fY < ymax) {
2683 continue;
2684 }
2685
2686 // If there is more than 1 distinct point at the y-max, we take the
2687 // x-min and x-max of them and just subtract to compute the dir.
2688 if (pts[(index + 1) % n].fY == pts[index].fY) {
2689 int maxIndex;
2690 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2691 if (minIndex == maxIndex) {
2692 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002693 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002694 SkASSERT(pts[minIndex].fY == pts[index].fY);
2695 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2696 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2697 // we just subtract the indices, and let that auto-convert to
2698 // SkScalar, since we just want - or + to signal the direction.
2699 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002700 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002701 TRY_CROSSPROD:
2702 // Find a next and prev index to use for the cross-product test,
2703 // but we try to find pts that form non-zero vectors from pts[index]
2704 //
2705 // Its possible that we can't find two non-degenerate vectors, so
2706 // we have to guard our search (e.g. all the pts could be in the
2707 // same place).
2708
2709 // we pass n - 1 instead of -1 so we don't foul up % operator by
2710 // passing it a negative LH argument.
2711 int prev = find_diff_pt(pts, index, n, n - 1);
2712 if (prev == index) {
2713 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002714 continue;
2715 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002716 int next = find_diff_pt(pts, index, n, 1);
2717 SkASSERT(next != index);
2718 cross = cross_prod(pts[prev], pts[index], pts[next]);
2719 // if we get a zero and the points are horizontal, then we look at the spread in
2720 // x-direction. We really should continue to walk away from the degeneracy until
2721 // there is a divergence.
2722 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2723 // construct the subtract so we get the correct Direction below
2724 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002725 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002726 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002728 if (cross) {
2729 // record our best guess so far
2730 ymax = pts[index].fY;
2731 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002732 }
2733 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002734 if (ymaxCross) {
2735 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002736 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002737 return true;
2738 } else {
2739 return false;
2740 }
reed@google.comac8543f2012-01-30 20:51:25 +00002741}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002742
2743///////////////////////////////////////////////////////////////////////////////
2744
caryclark9aacd902015-12-14 08:38:09 -08002745static bool between(SkScalar a, SkScalar b, SkScalar c) {
2746 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2747 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2748 return (a - b) * (c - b) <= 0;
2749}
2750
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2752 SkScalar t) {
2753 SkScalar A = c3 + 3*(c1 - c2) - c0;
2754 SkScalar B = 3*(c2 - c1 - c1 + c0);
2755 SkScalar C = 3*(c1 - c0);
2756 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002757 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758}
2759
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760template <size_t N> static void find_minmax(const SkPoint pts[],
2761 SkScalar* minPtr, SkScalar* maxPtr) {
2762 SkScalar min, max;
2763 min = max = pts[0].fX;
2764 for (size_t i = 1; i < N; ++i) {
2765 min = SkMinScalar(min, pts[i].fX);
2766 max = SkMaxScalar(max, pts[i].fX);
2767 }
2768 *minPtr = min;
2769 *maxPtr = max;
2770}
2771
caryclark9cb5d752015-12-18 04:35:24 -08002772static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2773 if (start.fY == end.fY) {
2774 return between(start.fX, x, end.fX) && x != end.fX;
2775 } else {
2776 return x == start.fX && y == start.fY;
2777 }
2778}
2779
caryclark9aacd902015-12-14 08:38:09 -08002780static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002781 SkScalar y0 = pts[0].fY;
2782 SkScalar y3 = pts[3].fY;
2783
2784 int dir = 1;
2785 if (y0 > y3) {
2786 SkTSwap(y0, y3);
2787 dir = -1;
2788 }
2789 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002790 return 0;
2791 }
caryclark9cb5d752015-12-18 04:35:24 -08002792 if (checkOnCurve(x, y, pts[0], pts[3])) {
2793 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002794 return 0;
2795 }
caryclark9cb5d752015-12-18 04:35:24 -08002796 if (y == y3) {
2797 return 0;
2798 }
2799
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002800 // quickreject or quickaccept
2801 SkScalar min, max;
2802 find_minmax<4>(pts, &min, &max);
2803 if (x < min) {
2804 return 0;
2805 }
2806 if (x > max) {
2807 return dir;
2808 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002809
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002810 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002811 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002812 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002813 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002814 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002815 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002816 if (SkScalarNearlyEqual(xt, x)) {
2817 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2818 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002819 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002820 }
2821 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 return xt < x ? dir : 0;
2823}
2824
caryclark9aacd902015-12-14 08:38:09 -08002825static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002826 SkPoint dst[10];
2827 int n = SkChopCubicAtYExtrema(pts, dst);
2828 int w = 0;
2829 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002830 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 }
2832 return w;
2833}
2834
caryclark9aacd902015-12-14 08:38:09 -08002835static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2836 SkASSERT(src);
2837 SkASSERT(t >= 0 && t <= 1);
2838 SkScalar src2w = src[2] * w;
2839 SkScalar C = src[0];
2840 SkScalar A = src[4] - 2 * src2w + C;
2841 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002842 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002843}
2844
2845
2846static double conic_eval_denominator(SkScalar w, SkScalar t) {
2847 SkScalar B = 2 * (w - 1);
2848 SkScalar C = 1;
2849 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002850 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002851}
2852
2853static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2854 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 SkScalar y0 = pts[0].fY;
2856 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002857
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002858 int dir = 1;
2859 if (y0 > y2) {
2860 SkTSwap(y0, y2);
2861 dir = -1;
2862 }
caryclark9aacd902015-12-14 08:38:09 -08002863 if (y < y0 || y > y2) {
2864 return 0;
2865 }
caryclark9cb5d752015-12-18 04:35:24 -08002866 if (checkOnCurve(x, y, pts[0], pts[2])) {
2867 *onCurveCount += 1;
2868 return 0;
2869 }
caryclark9aacd902015-12-14 08:38:09 -08002870 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002871 return 0;
2872 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002873
caryclark9aacd902015-12-14 08:38:09 -08002874 SkScalar roots[2];
2875 SkScalar A = pts[2].fY;
2876 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2877 SkScalar C = pts[0].fY;
2878 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2879 B -= C; // B = b*w - w * yCept + yCept - a
2880 C -= y;
2881 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2882 SkASSERT(n <= 1);
2883 SkScalar xt;
2884 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002885 // zero roots are returned only when y0 == y
2886 // Need [0] if dir == 1
2887 // and [2] if dir == -1
2888 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002889 } else {
2890 SkScalar t = roots[0];
2891 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2892 }
2893 if (SkScalarNearlyEqual(xt, x)) {
2894 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2895 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002896 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002897 }
2898 }
2899 return xt < x ? dir : 0;
2900}
2901
2902static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2903 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2904 if (y0 == y1) {
2905 return true;
2906 }
2907 if (y0 < y1) {
2908 return y1 <= y2;
2909 } else {
2910 return y1 >= y2;
2911 }
2912}
2913
2914static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2915 int* onCurveCount) {
2916 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002917 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002918 // If the data points are very large, the conic may not be monotonic but may also
2919 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002920 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2921 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2922 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002923 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2924 }
2925 return w;
2926}
2927
2928static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2929 SkScalar y0 = pts[0].fY;
2930 SkScalar y2 = pts[2].fY;
2931
2932 int dir = 1;
2933 if (y0 > y2) {
2934 SkTSwap(y0, y2);
2935 dir = -1;
2936 }
2937 if (y < y0 || y > y2) {
2938 return 0;
2939 }
caryclark9cb5d752015-12-18 04:35:24 -08002940 if (checkOnCurve(x, y, pts[0], pts[2])) {
2941 *onCurveCount += 1;
2942 return 0;
2943 }
caryclark9aacd902015-12-14 08:38:09 -08002944 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002945 return 0;
2946 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002947 // bounds check on X (not required. is it faster?)
2948#if 0
2949 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2950 return 0;
2951 }
2952#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002953
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002954 SkScalar roots[2];
2955 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2956 2 * (pts[1].fY - pts[0].fY),
2957 pts[0].fY - y,
2958 roots);
2959 SkASSERT(n <= 1);
2960 SkScalar xt;
2961 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002962 // zero roots are returned only when y0 == y
2963 // Need [0] if dir == 1
2964 // and [2] if dir == -1
2965 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002966 } else {
2967 SkScalar t = roots[0];
2968 SkScalar C = pts[0].fX;
2969 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2970 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002971 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002972 }
caryclark9aacd902015-12-14 08:38:09 -08002973 if (SkScalarNearlyEqual(xt, x)) {
2974 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2975 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002976 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002977 }
2978 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002979 return xt < x ? dir : 0;
2980}
2981
caryclark9aacd902015-12-14 08:38:09 -08002982static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 SkPoint dst[5];
2984 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002985
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2987 n = SkChopQuadAtYExtrema(pts, dst);
2988 pts = dst;
2989 }
caryclark9aacd902015-12-14 08:38:09 -08002990 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002991 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002992 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 }
2994 return w;
2995}
2996
caryclark9aacd902015-12-14 08:38:09 -08002997static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 SkScalar x0 = pts[0].fX;
2999 SkScalar y0 = pts[0].fY;
3000 SkScalar x1 = pts[1].fX;
3001 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003002
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003003 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005 int dir = 1;
3006 if (y0 > y1) {
3007 SkTSwap(y0, y1);
3008 dir = -1;
3009 }
caryclark9aacd902015-12-14 08:38:09 -08003010 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003011 return 0;
3012 }
caryclark9cb5d752015-12-18 04:35:24 -08003013 if (checkOnCurve(x, y, pts[0], pts[1])) {
3014 *onCurveCount += 1;
3015 return 0;
3016 }
3017 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003018 return 0;
3019 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003020 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003021
caryclark9aacd902015-12-14 08:38:09 -08003022 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003023 // zero cross means the point is on the line, and since the case where
3024 // y of the query point is at the end point is handled above, we can be
3025 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003026 if (x != x1 || y != pts[1].fY) {
3027 *onCurveCount += 1;
3028 }
caryclark9aacd902015-12-14 08:38:09 -08003029 dir = 0;
3030 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003031 dir = 0;
3032 }
3033 return dir;
3034}
3035
caryclark9aacd902015-12-14 08:38:09 -08003036static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3037 SkTDArray<SkVector>* tangents) {
3038 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3039 && !between(pts[2].fY, y, pts[3].fY)) {
3040 return;
3041 }
3042 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3043 && !between(pts[2].fX, x, pts[3].fX)) {
3044 return;
3045 }
3046 SkPoint dst[10];
3047 int n = SkChopCubicAtYExtrema(pts, dst);
3048 for (int i = 0; i <= n; ++i) {
3049 SkPoint* c = &dst[i * 3];
3050 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003051 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003052 continue;
mbarbella276e6332016-05-31 14:44:01 -07003053 }
caryclark9aacd902015-12-14 08:38:09 -08003054 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3055 if (!SkScalarNearlyEqual(x, xt)) {
3056 continue;
3057 }
3058 SkVector tangent;
3059 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3060 tangents->push(tangent);
3061 }
3062}
3063
3064static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3065 SkTDArray<SkVector>* tangents) {
3066 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3067 return;
3068 }
3069 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3070 return;
3071 }
3072 SkScalar roots[2];
3073 SkScalar A = pts[2].fY;
3074 SkScalar B = pts[1].fY * w - y * w + y;
3075 SkScalar C = pts[0].fY;
3076 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3077 B -= C; // B = b*w - w * yCept + yCept - a
3078 C -= y;
3079 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3080 for (int index = 0; index < n; ++index) {
3081 SkScalar t = roots[index];
3082 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3083 if (!SkScalarNearlyEqual(x, xt)) {
3084 continue;
3085 }
3086 SkConic conic(pts, w);
3087 tangents->push(conic.evalTangentAt(t));
3088 }
3089}
3090
3091static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3092 SkTDArray<SkVector>* tangents) {
3093 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3094 return;
3095 }
3096 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3097 return;
3098 }
3099 SkScalar roots[2];
3100 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3101 2 * (pts[1].fY - pts[0].fY),
3102 pts[0].fY - y,
3103 roots);
3104 for (int index = 0; index < n; ++index) {
3105 SkScalar t = roots[index];
3106 SkScalar C = pts[0].fX;
3107 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3108 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003109 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003110 if (!SkScalarNearlyEqual(x, xt)) {
3111 continue;
3112 }
3113 tangents->push(SkEvalQuadTangentAt(pts, t));
3114 }
3115}
3116
3117static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3118 SkTDArray<SkVector>* tangents) {
3119 SkScalar y0 = pts[0].fY;
3120 SkScalar y1 = pts[1].fY;
3121 if (!between(y0, y, y1)) {
3122 return;
3123 }
3124 SkScalar x0 = pts[0].fX;
3125 SkScalar x1 = pts[1].fX;
3126 if (!between(x0, x, x1)) {
3127 return;
3128 }
3129 SkScalar dx = x1 - x0;
3130 SkScalar dy = y1 - y0;
3131 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3132 return;
3133 }
3134 SkVector v;
3135 v.set(dx, dy);
3136 tangents->push(v);
3137}
3138
reed@google.com4db592c2013-10-30 17:39:43 +00003139static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3140 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3141}
3142
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003143bool SkPath::contains(SkScalar x, SkScalar y) const {
3144 bool isInverse = this->isInverseFillType();
3145 if (this->isEmpty()) {
3146 return isInverse;
3147 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003148
reed@google.com4db592c2013-10-30 17:39:43 +00003149 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003150 return isInverse;
3151 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003152
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003153 SkPath::Iter iter(*this, true);
3154 bool done = false;
3155 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003156 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003157 do {
3158 SkPoint pts[4];
3159 switch (iter.next(pts, false)) {
3160 case SkPath::kMove_Verb:
3161 case SkPath::kClose_Verb:
3162 break;
3163 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003164 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003165 break;
3166 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003167 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003168 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003169 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003170 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003171 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003172 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003173 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003174 break;
3175 case SkPath::kDone_Verb:
3176 done = true;
3177 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003178 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003179 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003180 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3181 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3182 if (evenOddFill) {
3183 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003184 }
caryclark9aacd902015-12-14 08:38:09 -08003185 if (w) {
3186 return !isInverse;
3187 }
3188 if (onCurveCount <= 1) {
3189 return SkToBool(onCurveCount) ^ isInverse;
3190 }
3191 if ((onCurveCount & 1) || evenOddFill) {
3192 return SkToBool(onCurveCount & 1) ^ isInverse;
3193 }
halcanary9d524f22016-03-29 09:03:52 -07003194 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003195 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3196 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003197 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003198 SkTDArray<SkVector> tangents;
3199 do {
3200 SkPoint pts[4];
3201 int oldCount = tangents.count();
3202 switch (iter.next(pts, false)) {
3203 case SkPath::kMove_Verb:
3204 case SkPath::kClose_Verb:
3205 break;
3206 case SkPath::kLine_Verb:
3207 tangent_line(pts, x, y, &tangents);
3208 break;
3209 case SkPath::kQuad_Verb:
3210 tangent_quad(pts, x, y, &tangents);
3211 break;
3212 case SkPath::kConic_Verb:
3213 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3214 break;
3215 case SkPath::kCubic_Verb:
3216 tangent_cubic(pts, x, y, &tangents);
3217 break;
3218 case SkPath::kDone_Verb:
3219 done = true;
3220 break;
3221 }
3222 if (tangents.count() > oldCount) {
3223 int last = tangents.count() - 1;
3224 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003225 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003226 tangents.remove(last);
3227 } else {
3228 for (int index = 0; index < last; ++index) {
3229 const SkVector& test = tangents[index];
3230 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003231 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3232 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003233 tangents.remove(last);
3234 tangents.removeShuffle(index);
3235 break;
3236 }
3237 }
3238 }
3239 }
3240 } while (!done);
3241 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003242}
fmalitaaa0df4e2015-12-01 09:13:23 -08003243
3244int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3245 SkScalar w, SkPoint pts[], int pow2) {
3246 const SkConic conic(p0, p1, p2, w);
3247 return conic.chopIntoQuadsPOW2(pts, pow2);
3248}
bsalomonedc743a2016-06-01 09:42:31 -07003249
3250bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3251 unsigned* start) {
3252 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3253 return false;
3254 }
3255 SkPath::RawIter iter(path);
3256 SkPoint verbPts[4];
3257 SkPath::Verb v;
3258 SkPoint rectPts[5];
3259 int rectPtCnt = 0;
3260 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3261 switch (v) {
3262 case SkPath::kMove_Verb:
3263 if (0 != rectPtCnt) {
3264 return false;
3265 }
3266 rectPts[0] = verbPts[0];
3267 ++rectPtCnt;
3268 break;
3269 case SkPath::kLine_Verb:
3270 if (5 == rectPtCnt) {
3271 return false;
3272 }
3273 rectPts[rectPtCnt] = verbPts[1];
3274 ++rectPtCnt;
3275 break;
3276 case SkPath::kClose_Verb:
3277 if (4 == rectPtCnt) {
3278 rectPts[4] = rectPts[0];
3279 rectPtCnt = 5;
3280 }
3281 break;
3282 default:
3283 return false;
3284 }
3285 }
3286 if (rectPtCnt < 5) {
3287 return false;
3288 }
3289 if (rectPts[0] != rectPts[4]) {
3290 return false;
3291 }
bsalomon057ae8a2016-07-24 05:37:26 -07003292 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3293 // and pts 1 and 2 the opposite vertical or horizontal edge).
3294 bool vec03IsVertical;
3295 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3296 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3297 // Make sure it has non-zero width and height
3298 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003299 return false;
3300 }
bsalomon057ae8a2016-07-24 05:37:26 -07003301 vec03IsVertical = true;
3302 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3303 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3304 // Make sure it has non-zero width and height
3305 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3306 return false;
3307 }
3308 vec03IsVertical = false;
3309 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003310 return false;
3311 }
bsalomon057ae8a2016-07-24 05:37:26 -07003312 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3313 // set if it is on the bottom edge.
3314 unsigned sortFlags =
3315 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3316 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3317 switch (sortFlags) {
3318 case 0b00:
3319 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3320 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3321 *start = 0;
3322 break;
3323 case 0b01:
3324 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3325 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3326 *start = 1;
3327 break;
3328 case 0b10:
3329 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3330 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3331 *start = 3;
3332 break;
3333 case 0b11:
3334 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3335 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3336 *start = 2;
3337 break;
bsalomonedc743a2016-06-01 09:42:31 -07003338 }
bsalomonedc743a2016-06-01 09:42:31 -07003339 return true;
3340}
bsalomon21af9ca2016-08-25 12:29:23 -07003341
3342void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3343 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3344 SkASSERT(!oval.isEmpty());
3345 SkASSERT(sweepAngle);
3346
3347 path->reset();
3348 path->setIsVolatile(true);
3349 path->setFillType(SkPath::kWinding_FillType);
3350 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3351 path->addOval(oval);
3352 return;
3353 }
3354 if (useCenter) {
3355 path->moveTo(oval.centerX(), oval.centerY());
3356 }
3357 // Arc to mods at 360 and drawArc is not supposed to.
3358 bool forceMoveTo = !useCenter;
3359 while (sweepAngle <= -360.f) {
3360 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3361 startAngle -= 180.f;
3362 path->arcTo(oval, startAngle, -180.f, false);
3363 startAngle -= 180.f;
3364 forceMoveTo = false;
3365 sweepAngle += 360.f;
3366 }
3367 while (sweepAngle >= 360.f) {
3368 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3369 startAngle += 180.f;
3370 path->arcTo(oval, startAngle, 180.f, false);
3371 startAngle += 180.f;
3372 forceMoveTo = false;
3373 sweepAngle -= 360.f;
3374 }
3375 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3376 if (useCenter) {
3377 path->close();
3378 }
3379}
Mike Reed0d7dac82017-02-02 17:45:56 -08003380
3381///////////////////////////////////////////////////////////////////////////////////////////////////
3382#include "SkNx.h"
3383
3384static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3385 SkScalar ts[2];
3386 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3387 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3388 SkASSERT(n >= 0 && n <= 2);
3389 for (int i = 0; i < n; ++i) {
3390 extremas[i] = SkEvalQuadAt(src, ts[i]);
3391 }
3392 extremas[n] = src[2];
3393 return n + 1;
3394}
3395
3396static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3397 SkConic conic(src[0], src[1], src[2], w);
3398 SkScalar ts[2];
3399 int n = conic.findXExtrema(ts);
3400 n += conic.findYExtrema(&ts[n]);
3401 SkASSERT(n >= 0 && n <= 2);
3402 for (int i = 0; i < n; ++i) {
3403 extremas[i] = conic.evalAt(ts[i]);
3404 }
3405 extremas[n] = src[2];
3406 return n + 1;
3407}
3408
3409static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3410 SkScalar ts[4];
3411 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3412 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3413 SkASSERT(n >= 0 && n <= 4);
3414 for (int i = 0; i < n; ++i) {
3415 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3416 }
3417 extremas[n] = src[3];
3418 return n + 1;
3419}
3420
Mike Reed8d3196b2017-02-03 11:34:13 -05003421SkRect SkPath::computeTightBounds() const {
3422 if (0 == this->countVerbs()) {
3423 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003424 }
3425
Mike Reed8d3196b2017-02-03 11:34:13 -05003426 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3427 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003428 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003429
Mike Reed0d7dac82017-02-02 17:45:56 -08003430 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3431 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003432 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003433
3434 // initial with the first MoveTo, so we don't have to check inside the switch
3435 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003436 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003437 for (;;) {
3438 int count = 0;
3439 switch (iter.next(pts)) {
3440 case SkPath::kMove_Verb:
3441 extremas[0] = pts[0];
3442 count = 1;
3443 break;
3444 case SkPath::kLine_Verb:
3445 extremas[0] = pts[1];
3446 count = 1;
3447 break;
3448 case SkPath::kQuad_Verb:
3449 count = compute_quad_extremas(pts, extremas);
3450 break;
3451 case SkPath::kConic_Verb:
3452 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3453 break;
3454 case SkPath::kCubic_Verb:
3455 count = compute_cubic_extremas(pts, extremas);
3456 break;
3457 case SkPath::kClose_Verb:
3458 break;
3459 case SkPath::kDone_Verb:
3460 goto DONE;
3461 }
3462 for (int i = 0; i < count; ++i) {
3463 Sk2s tmp = from_point(extremas[i]);
3464 min = Sk2s::Min(min, tmp);
3465 max = Sk2s::Max(max, tmp);
3466 }
3467 }
3468DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003469 SkRect bounds;
3470 min.store((SkPoint*)&bounds.fLeft);
3471 max.store((SkPoint*)&bounds.fRight);
3472 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003473}
Cary Clarkdf429f32017-11-08 11:44:31 -05003474
3475bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3476 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3477}
3478
3479bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3480 const SkPoint& p3, bool exact) {
3481 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3482 SkPointPriv::EqualsWithinTolerance(p2, p3);
3483}
3484
3485bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3486 const SkPoint& p3, const SkPoint& p4, bool exact) {
3487 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3488 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3489 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3490 SkPointPriv::EqualsWithinTolerance(p3, p4);
3491}