blob: beb46eb7ee26f83305b22c29ce3c817672a349d4 [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
438 first,last,next direction state-machine:
439 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 Clark88ba9712018-04-12 14:00:24 -0400453 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400454 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
455 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
caryclark@google.com56f233a2012-11-19 13:06:06 +0000456 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400457 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400458 lineStart.set(0, 0);
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000459 int firstDirection = 0;
460 int lastDirection = 0;
461 int nextDirection = 0;
462 bool closedOrMoved = false;
Cary Clark8540e112018-04-11 14:30:27 -0400463 bool addedLine = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 bool autoClose = false;
caryclark95bc5f32015-04-08 08:34:15 -0700465 bool insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400466 bool accumulatingRect = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000467 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000468 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
caryclark95bc5f32015-04-08 08:34:15 -0700469 uint8_t verb = insertClose ? (uint8_t) kClose_Verb : fPathRef->atVerb(*currVerb);
470 switch (verb) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000471 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000472 savePts = pts;
caryclark@google.comf1316942011-07-26 19:54:45 +0000473 autoClose = true;
caryclark95bc5f32015-04-08 08:34:15 -0700474 insertClose = false;
Cary Clark8540e112018-04-11 14:30:27 -0400475 accumulatingRect = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000476 case kLine_Verb: {
Cary Clark8540e112018-04-11 14:30:27 -0400477 if (accumulatingRect) {
478 lastPt = pts;
479 }
Cary Clark88ba9712018-04-12 14:00:24 -0400480 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
481 SkVector lineDelta = lineEnd - lineStart;
482 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000483 return false; // diagonal
484 }
Cary Clark8540e112018-04-11 14:30:27 -0400485 addedLine = true;
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 Clark88ba9712018-04-12 14:00:24 -0400489 nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY);
caryclark@google.comf1316942011-07-26 19:54:45 +0000490 if (0 == corners) {
491 firstDirection = nextDirection;
Cary Clark88ba9712018-04-12 14:00:24 -0400492 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 corners = 1;
494 closedOrMoved = false;
495 break;
496 }
497 if (closedOrMoved) {
498 return false; // closed followed by a line
499 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000500 if (autoClose && nextDirection == firstDirection) {
501 break; // colinear with first
502 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 closedOrMoved = autoClose;
504 if (lastDirection != nextDirection) {
505 if (++corners > 4) {
506 return false; // too many direction changes
507 }
508 }
Cary Clark88ba9712018-04-12 14:00:24 -0400509 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 if (lastDirection == nextDirection) {
511 break; // colinear segment
512 }
513 // Possible values for corners are 2, 3, and 4.
514 // When corners == 3, nextDirection opposes firstDirection.
515 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000516 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000517 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
518 if ((directionCycle ^ turn) != nextDirection) {
519 return false; // direction didn't follow cycle
520 }
521 break;
522 }
523 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000524 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000525 case kCubic_Verb:
526 return false; // quadratic, cubic not allowed
527 case kMove_Verb:
caryclark95bc5f32015-04-08 08:34:15 -0700528 if (allowPartial && !autoClose && firstDirection) {
529 insertClose = true;
530 *currVerb -= 1; // try move again afterwards
531 goto addMissingClose;
532 }
Cary Clark8540e112018-04-11 14:30:27 -0400533 if (!addedLine) {
534 firstPt = pts;
535 accumulatingRect = true;
536 } else {
537 accumulatingRect = false;
538 }
Cary Clark88ba9712018-04-12 14:00:24 -0400539 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000540 closedOrMoved = true;
541 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000542 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000543 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000544 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000545 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000546 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000547 lastDirection = nextDirection;
caryclark95bc5f32015-04-08 08:34:15 -0700548addMissingClose:
549 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000550 }
551 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400552 if (corners < 3 || corners > 4) {
553 return false;
554 }
555 SkPoint closeXY = *firstPt - *lastPt;
Cary Clark58627cb2018-04-10 09:16:41 -0400556 // If autoClose, check if close generates diagonal
Cary Clark8540e112018-04-11 14:30:27 -0400557 bool result = 4 == corners && (closeXY.isZero() || (autoClose && (!closeXY.fX || !closeXY.fY)));
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000558 if (!result) {
559 // check if we are just an incomplete rectangle, in which case we can
560 // return true, but not claim to be closed.
561 // e.g.
562 // 3 sided rectangle
563 // 4 sided but the last edge is not long enough to reach the start
564 //
Cary Clark8540e112018-04-11 14:30:27 -0400565 if (closeXY.fX && closeXY.fY) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000566 return false; // we're diagonal, abort (can we ever reach this?)
567 }
Cary Clark8540e112018-04-11 14:30:27 -0400568 int closeDirection = rect_make_dir(closeXY.fX, closeXY.fY);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000569 // make sure the close-segment doesn't double-back on itself
Cary Clark8540e112018-04-11 14:30:27 -0400570 if (3 == corners || closeDirection == lastDirection) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000571 result = true;
572 autoClose = false; // we are not closed
573 }
574 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000575 if (savePts) {
576 *ptsPtr = savePts;
577 }
Cary Clark5c715182018-04-09 16:07:11 -0400578 if (result && rect) {
Cary Clark8540e112018-04-11 14:30:27 -0400579 ptrdiff_t count = lastPt - firstPt + 1;
Cary Clark5c715182018-04-09 16:07:11 -0400580 rect->set(firstPt, (int) count);
581 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000582 if (result && isClosed) {
583 *isClosed = autoClose;
584 }
585 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000586 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000587 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000588 return result;
589}
590
robertphillips4f662e62014-12-29 14:06:51 -0800591bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000592 SkDEBUGCODE(this->validate();)
593 int currVerb = 0;
594 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400595 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000596}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000597
caryclark95bc5f32015-04-08 08:34:15 -0700598bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000599 SkDEBUGCODE(this->validate();)
600 int currVerb = 0;
601 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000602 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400603 SkRect testRects[2];
604 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000605 return false;
606 }
Cary Clark5c715182018-04-09 16:07:11 -0400607 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 if (testRects[0].contains(testRects[1])) {
609 if (rects) {
610 rects[0] = testRects[0];
611 rects[1] = testRects[1];
612 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000613 if (dirs) {
614 dirs[0] = testDirs[0];
615 dirs[1] = testDirs[1];
616 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000617 return true;
618 }
619 if (testRects[1].contains(testRects[0])) {
620 if (rects) {
621 rects[0] = testRects[1];
622 rects[1] = testRects[0];
623 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000624 if (dirs) {
625 dirs[0] = testDirs[1];
626 dirs[1] = testDirs[0];
627 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000628 return true;
629 }
630 }
631 return false;
632}
633
Mike Reed0c3137c2018-02-20 13:57:05 -0500634bool SkPath::isOval(SkRect* bounds) const {
635 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
636}
637
638bool SkPath::isRRect(SkRRect* rrect) const {
639 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
640}
641
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642int SkPath::countPoints() const {
643 return fPathRef->countPoints();
644}
645
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000646int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000647 SkDEBUGCODE(this->validate();)
648
649 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000650 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800652 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000654}
655
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000656SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000657 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
658 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000659 }
660 return SkPoint::Make(0, 0);
661}
662
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000663int SkPath::countVerbs() const {
664 return fPathRef->countVerbs();
665}
666
667static inline void copy_verbs_reverse(uint8_t* inorderDst,
668 const uint8_t* reversedSrc,
669 int count) {
670 for (int i = 0; i < count; ++i) {
671 inorderDst[i] = reversedSrc[~i];
672 }
673}
674
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000675int SkPath::getVerbs(uint8_t dst[], int max) const {
676 SkDEBUGCODE(this->validate();)
677
678 SkASSERT(max >= 0);
679 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 int count = SkMin32(max, fPathRef->countVerbs());
681 copy_verbs_reverse(dst, fPathRef->verbs(), count);
682 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000683}
684
reed@google.com294dd7b2011-10-11 11:58:32 +0000685bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 SkDEBUGCODE(this->validate();)
687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000688 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000689 if (count > 0) {
690 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000692 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000693 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000694 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000695 if (lastPt) {
696 lastPt->set(0, 0);
697 }
698 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000699}
700
caryclarkaec25102015-04-29 08:28:30 -0700701void SkPath::setPt(int index, SkScalar x, SkScalar y) {
702 SkDEBUGCODE(this->validate();)
703
704 int count = fPathRef->countPoints();
705 if (count <= index) {
706 return;
707 } else {
708 SkPathRef::Editor ed(&fPathRef);
709 ed.atPoint(index)->set(x, y);
710 }
711}
712
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713void SkPath::setLastPt(SkScalar x, SkScalar y) {
714 SkDEBUGCODE(this->validate();)
715
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000716 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717 if (count == 0) {
718 this->moveTo(x, y);
719 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000720 SkPathRef::Editor ed(&fPathRef);
721 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722 }
723}
724
reed@google.com04863fa2011-05-15 04:08:24 +0000725void SkPath::setConvexity(Convexity c) {
726 if (fConvexity != c) {
727 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000728 }
729}
730
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731//////////////////////////////////////////////////////////////////////////////
732// Construction methods
733
reed026beb52015-06-10 14:23:15 -0700734#define DIRTY_AFTER_EDIT \
735 do { \
736 fConvexity = kUnknown_Convexity; \
737 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000738 } while (0)
739
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740void SkPath::incReserve(U16CPU inc) {
741 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000742 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000743 SkDEBUGCODE(this->validate();)
744}
745
746void SkPath::moveTo(SkScalar x, SkScalar y) {
747 SkDEBUGCODE(this->validate();)
748
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000749 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000751 // remember our index
752 fLastMoveToIndex = fPathRef->countPoints();
753
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000754 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700755
756 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000757}
758
759void SkPath::rMoveTo(SkScalar x, SkScalar y) {
760 SkPoint pt;
761 this->getLastPt(&pt);
762 this->moveTo(pt.fX + x, pt.fY + y);
763}
764
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000765void SkPath::injectMoveToIfNeeded() {
766 if (fLastMoveToIndex < 0) {
767 SkScalar x, y;
768 if (fPathRef->countVerbs() == 0) {
769 x = y = 0;
770 } else {
771 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
772 x = pt.fX;
773 y = pt.fY;
774 }
775 this->moveTo(x, y);
776 }
777}
778
reed@android.com8a1c16f2008-12-17 15:59:43 +0000779void SkPath::lineTo(SkScalar x, SkScalar y) {
780 SkDEBUGCODE(this->validate();)
781
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000782 this->injectMoveToIfNeeded();
783
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000784 SkPathRef::Editor ed(&fPathRef);
785 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786
reed@google.comb54455e2011-05-16 14:16:04 +0000787 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788}
789
790void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000791 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 SkPoint pt;
793 this->getLastPt(&pt);
794 this->lineTo(pt.fX + x, pt.fY + y);
795}
796
797void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
798 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000799
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000800 this->injectMoveToIfNeeded();
801
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000802 SkPathRef::Editor ed(&fPathRef);
803 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 pts[0].set(x1, y1);
805 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000806
reed@google.comb54455e2011-05-16 14:16:04 +0000807 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000808}
809
810void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000811 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812 SkPoint pt;
813 this->getLastPt(&pt);
814 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
815}
816
reed@google.com277c3f82013-05-31 15:17:50 +0000817void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
818 SkScalar w) {
819 // check for <= 0 or NaN with this test
820 if (!(w > 0)) {
821 this->lineTo(x2, y2);
822 } else if (!SkScalarIsFinite(w)) {
823 this->lineTo(x1, y1);
824 this->lineTo(x2, y2);
825 } else if (SK_Scalar1 == w) {
826 this->quadTo(x1, y1, x2, y2);
827 } else {
828 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000829
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000830 this->injectMoveToIfNeeded();
831
reed@google.com277c3f82013-05-31 15:17:50 +0000832 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000833 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000834 pts[0].set(x1, y1);
835 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000836
reed@google.com277c3f82013-05-31 15:17:50 +0000837 DIRTY_AFTER_EDIT;
838 }
839}
840
841void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
842 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000843 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000844 SkPoint pt;
845 this->getLastPt(&pt);
846 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
847}
848
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
850 SkScalar x3, SkScalar y3) {
851 SkDEBUGCODE(this->validate();)
852
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000853 this->injectMoveToIfNeeded();
854
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000855 SkPathRef::Editor ed(&fPathRef);
856 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 pts[0].set(x1, y1);
858 pts[1].set(x2, y2);
859 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860
reed@google.comb54455e2011-05-16 14:16:04 +0000861 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000862}
863
864void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
865 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000866 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 SkPoint pt;
868 this->getLastPt(&pt);
869 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
870 pt.fX + x3, pt.fY + y3);
871}
872
873void SkPath::close() {
874 SkDEBUGCODE(this->validate();)
875
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000876 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000877 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000878 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879 case kLine_Verb:
880 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000881 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 case kMove_Verb: {
884 SkPathRef::Editor ed(&fPathRef);
885 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000886 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 }
reed@google.com277c3f82013-05-31 15:17:50 +0000888 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000889 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000890 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000891 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000892 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000893 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000894 }
895 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000896
897 // signal that we need a moveTo to follow us (unless we're done)
898#if 0
899 if (fLastMoveToIndex >= 0) {
900 fLastMoveToIndex = ~fLastMoveToIndex;
901 }
902#else
903 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
904#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000905}
906
907///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000908
fmalitac08d53e2015-11-17 09:53:29 -0800909namespace {
910
911template <unsigned N>
912class PointIterator {
913public:
914 PointIterator(SkPath::Direction dir, unsigned startIndex)
915 : fCurrent(startIndex % N)
916 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
917
918 const SkPoint& current() const {
919 SkASSERT(fCurrent < N);
920 return fPts[fCurrent];
921 }
922
923 const SkPoint& next() {
924 fCurrent = (fCurrent + fAdvance) % N;
925 return this->current();
926 }
927
928protected:
929 SkPoint fPts[N];
930
931private:
932 unsigned fCurrent;
933 unsigned fAdvance;
934};
935
936class RectPointIterator : public PointIterator<4> {
937public:
938 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
939 : PointIterator(dir, startIndex) {
940
941 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
942 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
943 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
944 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
945 }
946};
947
948class OvalPointIterator : public PointIterator<4> {
949public:
950 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
951 : PointIterator(dir, startIndex) {
952
953 const SkScalar cx = oval.centerX();
954 const SkScalar cy = oval.centerY();
955
956 fPts[0] = SkPoint::Make(cx, oval.fTop);
957 fPts[1] = SkPoint::Make(oval.fRight, cy);
958 fPts[2] = SkPoint::Make(cx, oval.fBottom);
959 fPts[3] = SkPoint::Make(oval.fLeft, cy);
960 }
961};
962
963class RRectPointIterator : public PointIterator<8> {
964public:
965 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
966 : PointIterator(dir, startIndex) {
967
968 const SkRect& bounds = rrect.getBounds();
969 const SkScalar L = bounds.fLeft;
970 const SkScalar T = bounds.fTop;
971 const SkScalar R = bounds.fRight;
972 const SkScalar B = bounds.fBottom;
973
974 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
975 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
976 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
977 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
978 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
979 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
980 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
981 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
982 }
983};
984
985} // anonymous namespace
986
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000987static void assert_known_direction(int dir) {
988 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
989}
990
reed@android.com8a1c16f2008-12-17 15:59:43 +0000991void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800992 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993}
994
995void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
996 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -0800997 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
998}
999
1000void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001001 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001002 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001003 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001004 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001005 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001006
fmalitac08d53e2015-11-17 09:53:29 -08001007 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001008
fmalitac08d53e2015-11-17 09:53:29 -08001009 const int kVerbs = 5; // moveTo + 3x lineTo + close
1010 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001011
fmalitac08d53e2015-11-17 09:53:29 -08001012 RectPointIterator iter(rect, dir, startIndex);
1013
1014 this->moveTo(iter.current());
1015 this->lineTo(iter.next());
1016 this->lineTo(iter.next());
1017 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001018 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001019
1020 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001021}
1022
reed@google.com744faba2012-05-29 19:54:52 +00001023void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1024 SkDEBUGCODE(this->validate();)
1025 if (count <= 0) {
1026 return;
1027 }
1028
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001029 fLastMoveToIndex = fPathRef->countPoints();
1030
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001031 // +close makes room for the extra kClose_Verb
1032 SkPathRef::Editor ed(&fPathRef, count+close, count);
1033
1034 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001035 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001036 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1037 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001038 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001039
reed@google.com744faba2012-05-29 19:54:52 +00001040 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001041 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001042 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001043 }
1044
reed@google.com744faba2012-05-29 19:54:52 +00001045 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001046 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001047}
1048
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001049#include "SkGeometry.h"
1050
reedf90ea012015-01-29 12:03:58 -08001051static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1052 SkPoint* pt) {
1053 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001054 // Chrome uses this path to move into and out of ovals. If not
1055 // treated as a special case the moves can distort the oval's
1056 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001057 pt->set(oval.fRight, oval.centerY());
1058 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001059 } else if (0 == oval.width() && 0 == oval.height()) {
1060 // Chrome will sometimes create 0 radius round rects. Having degenerate
1061 // quad segments in the path prevents the path from being recognized as
1062 // a rect.
1063 // TODO: optimizing the case where only one of width or height is zero
1064 // should also be considered. This case, however, doesn't seem to be
1065 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001066 pt->set(oval.fRight, oval.fTop);
1067 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001068 }
reedf90ea012015-01-29 12:03:58 -08001069 return false;
1070}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001071
reedd5d27d92015-02-09 13:54:43 -08001072// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1073//
1074static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1075 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1076 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1077 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001078
1079 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001080 loss in radians-conversion and/or sin/cos, we may end up with coincident
1081 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1082 of drawing a nearly complete circle (good).
1083 e.g. canvas.drawArc(0, 359.99, ...)
1084 -vs- canvas.drawArc(0, 359.9, ...)
1085 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001086 */
reedd5d27d92015-02-09 13:54:43 -08001087 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001088 SkScalar sw = SkScalarAbs(sweepAngle);
1089 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1090 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1091 // make a guess at a tiny angle (in radians) to tweak by
1092 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1093 // not sure how much will be enough, so we use a loop
1094 do {
1095 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001096 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1097 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001098 }
1099 }
reedd5d27d92015-02-09 13:54:43 -08001100 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1101}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001102
reed9e779d42015-02-17 11:43:14 -08001103/**
1104 * If this returns 0, then the caller should just line-to the singlePt, else it should
1105 * ignore singlePt and append the specified number of conics.
1106 */
reedd5d27d92015-02-09 13:54:43 -08001107static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001108 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1109 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001110 SkMatrix matrix;
1111
1112 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1113 matrix.postTranslate(oval.centerX(), oval.centerY());
1114
reed9e779d42015-02-17 11:43:14 -08001115 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1116 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001117 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001118 }
1119 return count;
reedd5d27d92015-02-09 13:54:43 -08001120}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001121
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001122void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001123 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001124 SkRRect rrect;
1125 rrect.setRectRadii(rect, (const SkVector*) radii);
1126 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127}
1128
reed@google.com4ed0fb72012-12-12 20:48:18 +00001129void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001130 // legacy start indices: 6 (CW) and 7(CCW)
1131 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1132}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001133
fmalitac08d53e2015-11-17 09:53:29 -08001134void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1135 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001136
caryclarkda707bf2015-11-19 14:47:43 -08001137 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001138 const SkRect& bounds = rrect.getBounds();
1139
Brian Salomon0a241ce2017-12-15 11:31:05 -05001140 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001141 // degenerate(rect) => radii points are collapsing
1142 this->addRect(bounds, dir, (startIndex + 1) / 2);
1143 } else if (rrect.isOval()) {
1144 // degenerate(oval) => line points are collapsing
1145 this->addOval(bounds, dir, startIndex / 2);
1146 } else {
1147 fFirstDirection = this->hasOnlyMoveTos() ?
1148 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1149
1150 SkAutoPathBoundsUpdate apbu(this, bounds);
1151 SkAutoDisableDirectionCheck addc(this);
1152
1153 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1154 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1155 const SkScalar weight = SK_ScalarRoot2Over2;
1156
1157 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1158 const int kVerbs = startsWithConic
1159 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1160 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1161 this->incReserve(kVerbs);
1162
1163 RRectPointIterator rrectIter(rrect, dir, startIndex);
1164 // Corner iterator indices follow the collapsed radii model,
1165 // adjusted such that the start pt is "behind" the radii start pt.
1166 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1167 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1168
1169 this->moveTo(rrectIter.current());
1170 if (startsWithConic) {
1171 for (unsigned i = 0; i < 3; ++i) {
1172 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1173 this->lineTo(rrectIter.next());
1174 }
1175 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1176 // final lineTo handled by close().
1177 } else {
1178 for (unsigned i = 0; i < 4; ++i) {
1179 this->lineTo(rrectIter.next());
1180 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1181 }
1182 }
1183 this->close();
1184
caryclarkda707bf2015-11-19 14:47:43 -08001185 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001186 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001187
fmalitac08d53e2015-11-17 09:53:29 -08001188 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1189 }
1190
1191 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001192}
1193
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001194bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001195 int count = fPathRef->countVerbs();
1196 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1197 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001198 if (*verbs == kLine_Verb ||
1199 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001200 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001201 *verbs == kCubic_Verb) {
1202 return false;
1203 }
1204 ++verbs;
1205 }
1206 return true;
1207}
1208
Brian Osmana2318572017-07-10 15:09:26 -04001209bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1210 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001211 if (count < 2) {
1212 return true;
1213 }
Brian Osmana2318572017-07-10 15:09:26 -04001214 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001215 const SkPoint& first = *pts;
1216 for (int index = 1; index < count; ++index) {
1217 if (first != pts[index]) {
1218 return false;
1219 }
1220 }
1221 return true;
1222}
1223
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001224void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1225 Direction dir) {
1226 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001227
humper@google.com75e3ca12013-04-08 21:44:11 +00001228 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001229 return;
1230 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001231
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001232 SkRRect rrect;
1233 rrect.setRectXY(rect, rx, ry);
1234 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001235}
1236
reed@android.com8a1c16f2008-12-17 15:59:43 +00001237void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001238 // legacy start index: 1
1239 this->addOval(oval, dir, 1);
1240}
1241
1242void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001243 assert_known_direction(dir);
1244
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001245 /* If addOval() is called after previous moveTo(),
1246 this path is still marked as an oval. This is used to
1247 fit into WebKit's calling sequences.
1248 We can't simply check isEmpty() in this case, as additional
1249 moveTo() would mark the path non empty.
1250 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001251 bool isOval = hasOnlyMoveTos();
1252 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001253 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001254 } else {
reed026beb52015-06-10 14:23:15 -07001255 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001256 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001257
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001258 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 SkAutoPathBoundsUpdate apbu(this, oval);
1260
fmalitac08d53e2015-11-17 09:53:29 -08001261 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1262 const int kVerbs = 6; // moveTo + 4x conicTo + close
1263 this->incReserve(kVerbs);
1264
1265 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1266 // The corner iterator pts are tracking "behind" the oval/radii pts.
1267 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001268 const SkScalar weight = SK_ScalarRoot2Over2;
1269
fmalitac08d53e2015-11-17 09:53:29 -08001270 this->moveTo(ovalIter.current());
1271 for (unsigned i = 0; i < 4; ++i) {
1272 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001273 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001274 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001275
fmalitac08d53e2015-11-17 09:53:29 -08001276 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1277
robertphillips@google.com466310d2013-12-03 16:43:54 +00001278 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001279
bsalomon78d58d12016-05-27 09:17:04 -07001280 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001281}
1282
reed@android.com8a1c16f2008-12-17 15:59:43 +00001283void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1284 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001285 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286 }
1287}
1288
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1290 bool forceMoveTo) {
1291 if (oval.width() < 0 || oval.height() < 0) {
1292 return;
1293 }
1294
reedf90ea012015-01-29 12:03:58 -08001295 if (fPathRef->countVerbs() == 0) {
1296 forceMoveTo = true;
1297 }
1298
1299 SkPoint lonePt;
1300 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1301 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1302 return;
1303 }
1304
reedd5d27d92015-02-09 13:54:43 -08001305 SkVector startV, stopV;
1306 SkRotationDirection dir;
1307 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1308
reed9e779d42015-02-17 11:43:14 -08001309 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001310
1311 // At this point, we know that the arc is not a lone point, but startV == stopV
1312 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1313 // cannot handle it.
1314 if (startV == stopV) {
1315 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1316 SkScalar radiusX = oval.width() / 2;
1317 SkScalar radiusY = oval.height() / 2;
1318 // We cannot use SkScalarSinCos function in the next line because
1319 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1320 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001321 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001322 // make sin(endAngle) to be 0 which will then draw a dot.
1323 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1324 oval.centerY() + radiusY * sk_float_sin(endAngle));
1325 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1326 return;
1327 }
1328
reedd5d27d92015-02-09 13:54:43 -08001329 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001330 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001331 if (count) {
1332 this->incReserve(count * 2 + 1);
1333 const SkPoint& pt = conics[0].fPts[0];
1334 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1335 for (int i = 0; i < count; ++i) {
1336 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1337 }
reed9e779d42015-02-17 11:43:14 -08001338 } else {
1339 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001340 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341}
1342
caryclark55d49052016-01-23 05:07:04 -08001343// This converts the SVG arc to conics.
1344// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1345// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1346// See also SVG implementation notes:
1347// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1348// Note that arcSweep bool value is flipped from the original implementation.
1349void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1350 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001351 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001352 SkPoint srcPts[2];
1353 this->getLastPt(&srcPts[0]);
1354 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1355 // joining the endpoints.
1356 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1357 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001358 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001359 return;
1360 }
1361 // If the current point and target point for the arc are identical, it should be treated as a
1362 // zero length path. This ensures continuity in animations.
1363 srcPts[1].set(x, y);
1364 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001365 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001366 return;
1367 }
1368 rx = SkScalarAbs(rx);
1369 ry = SkScalarAbs(ry);
1370 SkVector midPointDistance = srcPts[0] - srcPts[1];
1371 midPointDistance *= 0.5f;
1372
1373 SkMatrix pointTransform;
1374 pointTransform.setRotate(-angle);
1375
1376 SkPoint transformedMidPoint;
1377 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1378 SkScalar squareRx = rx * rx;
1379 SkScalar squareRy = ry * ry;
1380 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1381 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1382
1383 // Check if the radii are big enough to draw the arc, scale radii if not.
1384 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1385 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1386 if (radiiScale > 1) {
1387 radiiScale = SkScalarSqrt(radiiScale);
1388 rx *= radiiScale;
1389 ry *= radiiScale;
1390 }
1391
1392 pointTransform.setScale(1 / rx, 1 / ry);
1393 pointTransform.preRotate(-angle);
1394
1395 SkPoint unitPts[2];
1396 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1397 SkVector delta = unitPts[1] - unitPts[0];
1398
1399 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1400 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1401
1402 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1403 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1404 scaleFactor = -scaleFactor;
1405 }
1406 delta.scale(scaleFactor);
1407 SkPoint centerPoint = unitPts[0] + unitPts[1];
1408 centerPoint *= 0.5f;
1409 centerPoint.offset(-delta.fY, delta.fX);
1410 unitPts[0] -= centerPoint;
1411 unitPts[1] -= centerPoint;
1412 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1413 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1414 SkScalar thetaArc = theta2 - theta1;
1415 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1416 thetaArc += SK_ScalarPI * 2;
1417 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1418 thetaArc -= SK_ScalarPI * 2;
1419 }
1420 pointTransform.setRotate(angle);
1421 pointTransform.preScale(rx, ry);
1422
Cary Clark1acd3c72017-12-08 11:37:01 -05001423#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001424 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001425#else
1426 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1427 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1428#endif
caryclark55d49052016-01-23 05:07:04 -08001429 SkScalar thetaWidth = thetaArc / segments;
1430 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1431 if (!SkScalarIsFinite(t)) {
1432 return;
1433 }
1434 SkScalar startTheta = theta1;
1435 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001436#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1437 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1438 return scalar == SkScalarFloorToScalar(scalar);
1439 };
1440 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1441 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1442 scalar_is_integer(x) && scalar_is_integer(y);
1443#endif
caryclark55d49052016-01-23 05:07:04 -08001444 for (int i = 0; i < segments; ++i) {
1445 SkScalar endTheta = startTheta + thetaWidth;
1446 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1447
1448 unitPts[1].set(cosEndTheta, sinEndTheta);
1449 unitPts[1] += centerPoint;
1450 unitPts[0] = unitPts[1];
1451 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1452 SkPoint mapped[2];
1453 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001454 /*
1455 Computing the arc width introduces rounding errors that cause arcs to start
1456 outside their marks. A round rect may lose convexity as a result. If the input
1457 values are on integers, place the conic on integers as well.
1458 */
1459#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1460 if (expectIntegers) {
1461 SkScalar* mappedScalars = &mapped[0].fX;
1462 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1463 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1464 }
1465 }
1466#endif
caryclark55d49052016-01-23 05:07:04 -08001467 this->conicTo(mapped[0], mapped[1], w);
1468 startTheta = endTheta;
1469 }
1470}
1471
1472void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1473 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1474 SkPoint currentPoint;
1475 this->getLastPt(&currentPoint);
1476 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1477}
1478
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001479void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 if (oval.isEmpty() || 0 == sweepAngle) {
1481 return;
1482 }
1483
1484 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1485
1486 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001487 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1488 // See SkPath::addOval() docs.
1489 SkScalar startOver90 = startAngle / 90.f;
1490 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1491 SkScalar error = startOver90 - startOver90I;
1492 if (SkScalarNearlyEqual(error, 0)) {
1493 // Index 1 is at startAngle == 0.
1494 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1495 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1496 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1497 (unsigned) startIndex);
1498 return;
1499 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 }
bsalomon1978ce22016-05-31 10:42:16 -07001501 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502}
1503
1504/*
1505 Need to handle the case when the angle is sharp, and our computed end-points
1506 for the arc go behind pt1 and/or p2...
1507*/
reedc7789042015-01-29 12:59:11 -08001508void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001509 if (radius == 0) {
1510 this->lineTo(x1, y1);
1511 return;
1512 }
1513
1514 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001515
reed@android.com8a1c16f2008-12-17 15:59:43 +00001516 // need to know our prev pt so we can construct tangent vectors
1517 {
1518 SkPoint start;
1519 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001520 // Handle degenerate cases by adding a line to the first point and
1521 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 before.setNormalize(x1 - start.fX, y1 - start.fY);
1523 after.setNormalize(x2 - x1, y2 - y1);
1524 }
reed@google.comabf15c12011-01-18 20:35:51 +00001525
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526 SkScalar cosh = SkPoint::DotProduct(before, after);
1527 SkScalar sinh = SkPoint::CrossProduct(before, after);
1528
1529 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001530 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001531 return;
1532 }
reed@google.comabf15c12011-01-18 20:35:51 +00001533
Mike Reeda99b6ce2017-02-04 11:04:26 -05001534 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001535
Mike Reeda99b6ce2017-02-04 11:04:26 -05001536 SkScalar xx = x1 - dist * before.fX;
1537 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001538 after.setLength(dist);
1539 this->lineTo(xx, yy);
1540 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1541 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542}
1543
1544///////////////////////////////////////////////////////////////////////////////
1545
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001546void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 SkMatrix matrix;
1548
1549 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001550 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001551}
1552
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001553void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001554 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001556 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001557 SkPoint pts[4];
1558 Verb verb;
1559
Cary Clark9480d822017-10-19 18:01:13 -04001560 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001561 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562 while ((verb = iter.next(pts)) != kDone_Verb) {
1563 switch (verb) {
1564 case kMove_Verb:
1565 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001566 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1567 injectMoveToIfNeeded(); // In case last contour is closed
1568 this->lineTo(pts[0]);
1569 } else {
1570 this->moveTo(pts[0]);
1571 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001572 break;
1573 case kLine_Verb:
1574 proc(matrix, &pts[1], &pts[1], 1);
1575 this->lineTo(pts[1]);
1576 break;
1577 case kQuad_Verb:
1578 proc(matrix, &pts[1], &pts[1], 2);
1579 this->quadTo(pts[1], pts[2]);
1580 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001581 case kConic_Verb:
1582 proc(matrix, &pts[1], &pts[1], 2);
1583 this->conicTo(pts[1], pts[2], iter.conicWeight());
1584 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585 case kCubic_Verb:
1586 proc(matrix, &pts[1], &pts[1], 3);
1587 this->cubicTo(pts[1], pts[2], pts[3]);
1588 break;
1589 case kClose_Verb:
1590 this->close();
1591 break;
1592 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001593 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001594 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001595 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 }
1597}
1598
1599///////////////////////////////////////////////////////////////////////////////
1600
reed@google.com277c3f82013-05-31 15:17:50 +00001601static int pts_in_verb(unsigned verb) {
1602 static const uint8_t gPtsInVerb[] = {
1603 1, // kMove
1604 1, // kLine
1605 2, // kQuad
1606 2, // kConic
1607 3, // kCubic
1608 0, // kClose
1609 0 // kDone
1610 };
1611
1612 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1613 return gPtsInVerb[verb];
1614}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001615
reed@android.com8a1c16f2008-12-17 15:59:43 +00001616// ignore the last point of the 1st contour
1617void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001618 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1619 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620 return;
1621 }
caryclark51c56782016-11-07 05:09:28 -08001622 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1623 SkASSERT(verbsEnd[0] == kMove_Verb);
1624 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1625 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626
caryclark51c56782016-11-07 05:09:28 -08001627 while (verbs < verbsEnd) {
1628 uint8_t v = *verbs++;
1629 pts -= pts_in_verb(v);
1630 switch (v) {
1631 case kMove_Verb:
1632 // if the path has multiple contours, stop after reversing the last
1633 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001634 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001635 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 break;
1637 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001638 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001639 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001640 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001641 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001642 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001644 this->cubicTo(pts[2], pts[1], pts[0]);
1645 break;
1646 case kClose_Verb:
1647 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001648 break;
1649 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001650 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651 break;
1652 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001653 }
1654}
1655
reed@google.com63d73742012-01-10 15:33:12 +00001656void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001657 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001658
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001659 const SkPoint* pts = src.fPathRef->pointsEnd();
1660 // we will iterator through src's verbs backwards
1661 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1662 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001663 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001664
1665 bool needMove = true;
1666 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001667 while (verbs < verbsEnd) {
1668 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001669 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001670
1671 if (needMove) {
1672 --pts;
1673 this->moveTo(pts->fX, pts->fY);
1674 needMove = false;
1675 }
1676 pts -= n;
1677 switch (v) {
1678 case kMove_Verb:
1679 if (needClose) {
1680 this->close();
1681 needClose = false;
1682 }
1683 needMove = true;
1684 pts += 1; // so we see the point in "if (needMove)" above
1685 break;
1686 case kLine_Verb:
1687 this->lineTo(pts[0]);
1688 break;
1689 case kQuad_Verb:
1690 this->quadTo(pts[1], pts[0]);
1691 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001692 case kConic_Verb:
1693 this->conicTo(pts[1], pts[0], *--conicWeights);
1694 break;
reed@google.com63d73742012-01-10 15:33:12 +00001695 case kCubic_Verb:
1696 this->cubicTo(pts[2], pts[1], pts[0]);
1697 break;
1698 case kClose_Verb:
1699 needClose = true;
1700 break;
1701 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001702 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001703 }
1704 }
1705}
1706
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707///////////////////////////////////////////////////////////////////////////////
1708
1709void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1710 SkMatrix matrix;
1711
1712 matrix.setTranslate(dx, dy);
1713 this->transform(matrix, dst);
1714}
1715
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1717 int level = 2) {
1718 if (--level >= 0) {
1719 SkPoint tmp[7];
1720
1721 SkChopCubicAtHalf(pts, tmp);
1722 subdivide_cubic_to(path, &tmp[0], level);
1723 subdivide_cubic_to(path, &tmp[3], level);
1724 } else {
1725 path->cubicTo(pts[1], pts[2], pts[3]);
1726 }
1727}
1728
1729void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1730 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001731 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 dst = (SkPath*)this;
1733 }
1734
tomhudson@google.com8d430182011-06-06 19:11:19 +00001735 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 SkPath tmp;
1737 tmp.fFillType = fFillType;
1738
1739 SkPath::Iter iter(*this, false);
1740 SkPoint pts[4];
1741 SkPath::Verb verb;
1742
reed@google.com4a3b7142012-05-16 17:16:46 +00001743 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001744 switch (verb) {
1745 case kMove_Verb:
1746 tmp.moveTo(pts[0]);
1747 break;
1748 case kLine_Verb:
1749 tmp.lineTo(pts[1]);
1750 break;
1751 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001752 // promote the quad to a conic
1753 tmp.conicTo(pts[1], pts[2],
1754 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001756 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001757 tmp.conicTo(pts[1], pts[2],
1758 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001759 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001760 case kCubic_Verb:
1761 subdivide_cubic_to(&tmp, pts);
1762 break;
1763 case kClose_Verb:
1764 tmp.close();
1765 break;
1766 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001767 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001768 break;
1769 }
1770 }
1771
1772 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773 SkPathRef::Editor ed(&dst->fPathRef);
1774 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001775 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001777 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1778
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001781 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001782 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001784
reed026beb52015-06-10 14:23:15 -07001785 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1786 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001787 } else {
1788 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001789 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1790 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001791 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001792 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1793 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001794 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001795 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001796 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001797 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001798 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001799 }
1800 }
1801
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 SkDEBUGCODE(dst->validate();)
1803 }
1804}
1805
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806///////////////////////////////////////////////////////////////////////////////
1807///////////////////////////////////////////////////////////////////////////////
1808
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001810 kEmptyContour_SegmentState, // The current contour is empty. We may be
1811 // starting processing or we may have just
1812 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1814 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1815 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816};
1817
1818SkPath::Iter::Iter() {
1819#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001820 fPts = nullptr;
1821 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001823 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001824 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001825#endif
1826 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001827 fVerbs = nullptr;
1828 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 fNeedClose = false;
1830}
1831
1832SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1833 this->setPath(path, forceClose);
1834}
1835
1836void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001837 fPts = path.fPathRef->points();
1838 fVerbs = path.fPathRef->verbs();
1839 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001840 fConicWeights = path.fPathRef->conicWeights();
1841 if (fConicWeights) {
1842 fConicWeights -= 1; // begin one behind
1843 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001845 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 fForceClose = SkToU8(forceClose);
1847 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001848 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849}
1850
1851bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001852 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001853 return false;
1854 }
1855 if (fForceClose) {
1856 return true;
1857 }
1858
1859 const uint8_t* verbs = fVerbs;
1860 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001861
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001862 if (kMove_Verb == *(verbs - 1)) {
1863 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864 }
1865
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001866 while (verbs > stop) {
1867 // verbs points one beyond the current verb, decrement first.
1868 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869 if (kMove_Verb == v) {
1870 break;
1871 }
1872 if (kClose_Verb == v) {
1873 return true;
1874 }
1875 }
1876 return false;
1877}
1878
1879SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001880 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001881 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001882 // A special case: if both points are NaN, SkPoint::operation== returns
1883 // false, but the iterator expects that they are treated as the same.
1884 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001885 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1886 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001887 return kClose_Verb;
1888 }
1889
reed@google.com9e25dbf2012-05-15 17:05:38 +00001890 pts[0] = fLastPt;
1891 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001892 fLastPt = fMoveTo;
1893 fCloseLine = true;
1894 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001895 } else {
1896 pts[0] = fMoveTo;
1897 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001898 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899}
1900
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 if (fSegmentState == kAfterMove_SegmentState) {
1903 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001904 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001905 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001906 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1908 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001909 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001911}
1912
caryclarke8c56662015-07-14 11:19:26 -07001913void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914 // We need to step over anything that will not move the current draw point
1915 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001916 const uint8_t* lastMoveVerb = nullptr;
1917 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001918 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 SkPoint lastPt = fLastPt;
1920 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001921 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001922 switch (verb) {
1923 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924 // Keep a record of this most recent move
1925 lastMoveVerb = fVerbs;
1926 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001927 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001928 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001929 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 fPts++;
1931 break;
1932
1933 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001934 // A close when we are in a segment is always valid except when it
1935 // follows a move which follows a segment.
1936 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 return;
1938 }
1939 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001940 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 break;
1942
1943 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001944 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 if (lastMoveVerb) {
1946 fVerbs = lastMoveVerb;
1947 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001948 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001949 return;
1950 }
1951 return;
1952 }
1953 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001954 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001955 fPts++;
1956 break;
1957
reed@google.com277c3f82013-05-31 15:17:50 +00001958 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001960 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001961 if (lastMoveVerb) {
1962 fVerbs = lastMoveVerb;
1963 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001964 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001965 return;
1966 }
1967 return;
1968 }
1969 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001970 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001971 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001972 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001973 break;
1974
1975 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001976 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001977 if (lastMoveVerb) {
1978 fVerbs = lastMoveVerb;
1979 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001980 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001981 return;
1982 }
1983 return;
1984 }
1985 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001986 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001987 fPts += 3;
1988 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001989
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001990 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001991 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 }
1993 }
1994}
1995
reed@google.com4a3b7142012-05-16 17:16:46 +00001996SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001997 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002000 // Close the curve if requested and if there is some curve to close
2001 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002002 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002003 return kLine_Verb;
2004 }
2005 fNeedClose = false;
2006 return kClose_Verb;
2007 }
2008 return kDone_Verb;
2009 }
2010
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002011 // fVerbs is one beyond the current verb, decrement first
2012 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002013 const SkPoint* SK_RESTRICT srcPts = fPts;
2014 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015
2016 switch (verb) {
2017 case kMove_Verb:
2018 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002019 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 verb = this->autoClose(pts);
2021 if (verb == kClose_Verb) {
2022 fNeedClose = false;
2023 }
2024 return (Verb)verb;
2025 }
2026 if (fVerbs == fVerbStop) { // might be a trailing moveto
2027 return kDone_Verb;
2028 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002029 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002030 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002032 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002033 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002034 fNeedClose = fForceClose;
2035 break;
2036 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002037 pts[0] = this->cons_moveTo();
2038 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002039 fLastPt = srcPts[0];
2040 fCloseLine = false;
2041 srcPts += 1;
2042 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002043 case kConic_Verb:
2044 fConicWeights += 1;
2045 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002047 pts[0] = this->cons_moveTo();
2048 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002049 fLastPt = srcPts[1];
2050 srcPts += 2;
2051 break;
2052 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002053 pts[0] = this->cons_moveTo();
2054 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002055 fLastPt = srcPts[2];
2056 srcPts += 3;
2057 break;
2058 case kClose_Verb:
2059 verb = this->autoClose(pts);
2060 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002061 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002062 } else {
2063 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002064 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002066 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067 break;
2068 }
2069 fPts = srcPts;
2070 return (Verb)verb;
2071}
2072
2073///////////////////////////////////////////////////////////////////////////////
2074
Ben Wagner4d1955c2017-03-10 13:08:15 -05002075#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002076#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002077#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002078
reed@google.com51bbe752013-01-17 22:07:50 +00002079static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002080 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002081 str->append(label);
2082 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002083
reed@google.com51bbe752013-01-17 22:07:50 +00002084 const SkScalar* values = &pts[0].fX;
2085 count *= 2;
2086
2087 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002088 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002089 if (i < count - 1) {
2090 str->append(", ");
2091 }
2092 }
Mike Reed405b9d22018-01-09 15:11:08 -05002093 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002094 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002095 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002096 }
caryclark08fa28c2014-10-23 13:08:56 -07002097 str->append(");");
reede05fed02014-12-15 07:59:53 -08002098 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002099 str->append(" // ");
2100 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002101 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002102 if (i < count - 1) {
2103 str->append(", ");
2104 }
2105 }
2106 if (conicWeight >= 0) {
2107 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002108 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002109 }
2110 }
2111 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002112}
2113
caryclarke9562592014-09-15 09:26:09 -07002114void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002115 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 Iter iter(*this, forceClose);
2117 SkPoint pts[4];
2118 Verb verb;
2119
reed@google.com51bbe752013-01-17 22:07:50 +00002120 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002121 char const * const gFillTypeStrs[] = {
2122 "Winding",
2123 "EvenOdd",
2124 "InverseWinding",
2125 "InverseEvenOdd",
2126 };
2127 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2128 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002129 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130 switch (verb) {
2131 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002132 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 break;
2134 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002135 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136 break;
2137 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002138 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002140 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002141 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002142 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002143 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002144 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002145 break;
2146 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002147 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002148 break;
2149 default:
2150 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2151 verb = kDone_Verb; // stop the loop
2152 break;
2153 }
caryclark1049f122015-04-20 08:31:59 -07002154 if (!wStream && builder.size()) {
2155 SkDebugf("%s", builder.c_str());
2156 builder.reset();
2157 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002158 }
caryclark66a5d8b2014-06-24 08:30:15 -07002159 if (wStream) {
2160 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002161 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162}
2163
reed@android.come522ca52009-11-23 20:10:41 +00002164void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002165 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002166}
2167
2168void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002169 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002170}
2171
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002172
Cary Clark0461e9f2017-08-25 15:13:38 -04002173bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002174 if ((fFillType & ~3) != 0) {
2175 return false;
2176 }
reed@google.comabf15c12011-01-18 20:35:51 +00002177
djsollen@google.com077348c2012-10-22 20:23:32 +00002178#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002179 if (!fBoundsIsDirty) {
2180 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002181
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002182 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002183 if (SkToBool(fIsFinite) != isFinite) {
2184 return false;
2185 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002186
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002187 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002188 // if we're empty, fBounds may be empty but translated, so we can't
2189 // necessarily compare to bounds directly
2190 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2191 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002192 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2193 return false;
2194 }
reed@android.come522ca52009-11-23 20:10:41 +00002195 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002196 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002197 if (!fBounds.isEmpty()) {
2198 return false;
2199 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002200 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002201 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002202 if (!fBounds.contains(bounds)) {
2203 return false;
2204 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002205 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002206 }
reed@android.come522ca52009-11-23 20:10:41 +00002207 }
2208 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002209#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002210 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002211}
reed@android.come522ca52009-11-23 20:10:41 +00002212
reed@google.com04863fa2011-05-15 04:08:24 +00002213///////////////////////////////////////////////////////////////////////////////
2214
reed@google.com0b7b9822011-05-16 12:29:27 +00002215static int sign(SkScalar x) { return x < 0; }
2216#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002217
robertphillipsc506e302014-09-16 09:43:31 -07002218enum DirChange {
2219 kLeft_DirChange,
2220 kRight_DirChange,
2221 kStraight_DirChange,
2222 kBackwards_DirChange,
2223
2224 kInvalid_DirChange
2225};
2226
2227
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002228static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002229 // The error epsilon was empirically derived; worse case round rects
2230 // with a mid point outset by 2x float epsilon in tests had an error
2231 // of 12.
2232 const int epsilon = 16;
2233 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2234 return false;
2235 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002236 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002237 int aBits = SkFloatAs2sCompliment(compA);
2238 int bBits = SkFloatAs2sCompliment(compB);
2239 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002240}
2241
caryclarkb4216502015-03-02 13:02:34 -08002242static bool approximately_zero_when_compared_to(double x, double y) {
2243 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002244}
2245
caryclarkb4216502015-03-02 13:02:34 -08002246
reed@google.com04863fa2011-05-15 04:08:24 +00002247// only valid for a single contour
2248struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002249 Convexicator()
2250 : fPtCount(0)
2251 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002252 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002253 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002254 , fIsCurve(false)
2255 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002256 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002257 // warnings
djsollen2f124632016-04-29 13:53:05 -07002258 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002259 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002260 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002261 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002262 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002263
2264 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002265 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002266 }
2267
2268 SkPath::Convexity getConvexity() const { return fConvexity; }
2269
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002270 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002271 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002272
reed@google.com04863fa2011-05-15 04:08:24 +00002273 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002274 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002275 return;
2276 }
2277
2278 if (0 == fPtCount) {
2279 fCurrPt = pt;
2280 ++fPtCount;
2281 } else {
2282 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002283 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002284 if (!SkScalarIsFinite(lengthSqd)) {
2285 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002286 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002287 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002288 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002289 fCurrPt = pt;
2290 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002291 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002292 } else {
2293 SkASSERT(fPtCount > 2);
2294 this->addVec(vec);
2295 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002296
reed@google.com85b6e392011-05-15 20:25:17 +00002297 int sx = sign(vec.fX);
2298 int sy = sign(vec.fY);
2299 fDx += (sx != fSx);
2300 fDy += (sy != fSy);
2301 fSx = sx;
2302 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002303
reed@google.com85b6e392011-05-15 20:25:17 +00002304 if (fDx > 3 || fDy > 3) {
2305 fConvexity = SkPath::kConcave_Convexity;
2306 }
reed@google.com04863fa2011-05-15 04:08:24 +00002307 }
2308 }
2309 }
2310
2311 void close() {
2312 if (fPtCount > 2) {
2313 this->addVec(fFirstVec);
2314 }
2315 }
2316
caryclarkb4216502015-03-02 13:02:34 -08002317 DirChange directionChange(const SkVector& curVec) {
2318 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2319
2320 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2321 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2322 largest = SkTMax(largest, -smallest);
2323
2324 if (!almost_equal(largest, largest + cross)) {
2325 int sign = SkScalarSignAsInt(cross);
2326 if (sign) {
2327 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2328 }
2329 }
2330
2331 if (cross) {
2332 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2333 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2334 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2335 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2336 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2337 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2338 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2339 if (sign) {
2340 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2341 }
2342 }
2343 }
2344
Cary Clarkdf429f32017-11-08 11:44:31 -05002345 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2346 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2347 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2348 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002349 fLastVec.dot(curVec) < 0.0f) {
2350 return kBackwards_DirChange;
2351 }
2352
2353 return kStraight_DirChange;
2354 }
2355
Cary Clarkc8323aa2017-08-25 08:04:43 -04002356 bool hasBackwards() const {
2357 return fBackwards;
2358 }
caryclarkb4216502015-03-02 13:02:34 -08002359
caryclarkd3d1a982014-12-08 04:57:38 -08002360 bool isFinite() const {
2361 return fIsFinite;
2362 }
2363
caryclark5ccef572015-03-02 10:07:56 -08002364 void setCurve(bool isCurve) {
2365 fIsCurve = isCurve;
2366 }
2367
reed@google.com04863fa2011-05-15 04:08:24 +00002368private:
2369 void addVec(const SkVector& vec) {
2370 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002371 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002372 switch (dir) {
2373 case kLeft_DirChange: // fall through
2374 case kRight_DirChange:
2375 if (kInvalid_DirChange == fExpectedDir) {
2376 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002377 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2378 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002379 } else if (dir != fExpectedDir) {
2380 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002381 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002382 }
2383 fLastVec = vec;
2384 break;
2385 case kStraight_DirChange:
2386 break;
2387 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002388 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002389 // If any of the subsequent dir is non-backward, it'll be concave.
2390 // Otherwise, it's still convex.
2391 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002392 }
robertphillipsc506e302014-09-16 09:43:31 -07002393 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002394 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002395 break;
2396 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002397 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002398 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002399 }
2400 }
2401
caryclarkb4216502015-03-02 13:02:34 -08002402 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002403 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002405 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2406 // value with the current vec is deemed to be of a significant value.
2407 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002408 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002409 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002411 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002412 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002413 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002414 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002415 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002416};
2417
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002418SkPath::Convexity SkPath::internalGetConvexity() const {
2419 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002420 SkPoint pts[4];
2421 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002423
2424 int contourCount = 0;
2425 int count;
2426 Convexicator state;
2427
caryclarkd3d1a982014-12-08 04:57:38 -08002428 if (!isFinite()) {
2429 return kUnknown_Convexity;
2430 }
Brian Osman205a1262017-09-18 13:13:48 +00002431 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002432 switch (verb) {
2433 case kMove_Verb:
2434 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002435 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002436 return kConcave_Convexity;
2437 }
2438 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002439 // fall through
2440 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002441 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002442 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002443 break;
caryclark5ccef572015-03-02 10:07:56 -08002444 case kQuad_Verb:
2445 // fall through
2446 case kConic_Verb:
2447 // fall through
2448 case kCubic_Verb:
2449 count = 2 + (kCubic_Verb == verb);
2450 // As an additional enhancement, this could set curve true only
2451 // if the curve is nonlinear
2452 state.setCurve(true);
2453 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002454 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002455 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002456 state.close();
2457 count = 0;
2458 break;
2459 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002460 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002461 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002462 return kConcave_Convexity;
2463 }
2464
2465 for (int i = 1; i <= count; i++) {
2466 state.addPt(pts[i]);
2467 }
2468 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002469 if (!state.isFinite()) {
2470 return kUnknown_Convexity;
2471 }
reed@google.com04863fa2011-05-15 04:08:24 +00002472 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002473 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002474 return kConcave_Convexity;
2475 }
2476 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002477 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002478 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002479 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2480 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2481 fConvexity = Convexity::kConcave_Convexity;
2482 } else {
2483 fFirstDirection = state.getFirstDirection();
2484 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002485 }
2486 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002487}
reed@google.com69a99432012-01-10 18:00:10 +00002488
2489///////////////////////////////////////////////////////////////////////////////
2490
2491class ContourIter {
2492public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002493 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002494
2495 bool done() const { return fDone; }
2496 // if !done() then these may be called
2497 int count() const { return fCurrPtCount; }
2498 const SkPoint* pts() const { return fCurrPt; }
2499 void next();
2500
2501private:
2502 int fCurrPtCount;
2503 const SkPoint* fCurrPt;
2504 const uint8_t* fCurrVerb;
2505 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002506 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002507 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002508 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002509};
2510
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002511ContourIter::ContourIter(const SkPathRef& pathRef) {
2512 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002513 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002514 fCurrPt = pathRef.points();
2515 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002516 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002517 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002518 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002519 this->next();
2520}
2521
2522void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002523 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002524 fDone = true;
2525 }
2526 if (fDone) {
2527 return;
2528 }
2529
2530 // skip pts of prev contour
2531 fCurrPt += fCurrPtCount;
2532
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002533 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002534 int ptCount = 1; // moveTo
2535 const uint8_t* verbs = fCurrVerb;
2536
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002537 for (--verbs; verbs > fStopVerbs; --verbs) {
2538 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002539 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002540 goto CONTOUR_END;
2541 case SkPath::kLine_Verb:
2542 ptCount += 1;
2543 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002544 case SkPath::kConic_Verb:
2545 fCurrConicWeight += 1;
2546 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002547 case SkPath::kQuad_Verb:
2548 ptCount += 2;
2549 break;
2550 case SkPath::kCubic_Verb:
2551 ptCount += 3;
2552 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002553 case SkPath::kClose_Verb:
2554 break;
2555 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002556 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002557 break;
2558 }
2559 }
2560CONTOUR_END:
2561 fCurrPtCount = ptCount;
2562 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002563 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002564}
2565
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002566// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002567static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002568 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2569 // We may get 0 when the above subtracts underflow. We expect this to be
2570 // very rare and lazily promote to double.
2571 if (0 == cross) {
2572 double p0x = SkScalarToDouble(p0.fX);
2573 double p0y = SkScalarToDouble(p0.fY);
2574
2575 double p1x = SkScalarToDouble(p1.fX);
2576 double p1y = SkScalarToDouble(p1.fY);
2577
2578 double p2x = SkScalarToDouble(p2.fX);
2579 double p2y = SkScalarToDouble(p2.fY);
2580
2581 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2582 (p1y - p0y) * (p2x - p0x));
2583
2584 }
2585 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002586}
2587
reed@google.comc1ea60a2012-01-31 15:15:36 +00002588// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002589static int find_max_y(const SkPoint pts[], int count) {
2590 SkASSERT(count > 0);
2591 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002592 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002593 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002594 SkScalar y = pts[i].fY;
2595 if (y > max) {
2596 max = y;
2597 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002598 }
2599 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002600 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002601}
2602
reed@google.comcabaf1d2012-01-11 21:03:05 +00002603static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2604 int i = index;
2605 for (;;) {
2606 i = (i + inc) % n;
2607 if (i == index) { // we wrapped around, so abort
2608 break;
2609 }
2610 if (pts[index] != pts[i]) { // found a different point, success!
2611 break;
2612 }
2613 }
2614 return i;
2615}
2616
reed@google.comc1ea60a2012-01-31 15:15:36 +00002617/**
2618 * Starting at index, and moving forward (incrementing), find the xmin and
2619 * xmax of the contiguous points that have the same Y.
2620 */
2621static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2622 int* maxIndexPtr) {
2623 const SkScalar y = pts[index].fY;
2624 SkScalar min = pts[index].fX;
2625 SkScalar max = min;
2626 int minIndex = index;
2627 int maxIndex = index;
2628 for (int i = index + 1; i < n; ++i) {
2629 if (pts[i].fY != y) {
2630 break;
2631 }
2632 SkScalar x = pts[i].fX;
2633 if (x < min) {
2634 min = x;
2635 minIndex = i;
2636 } else if (x > max) {
2637 max = x;
2638 maxIndex = i;
2639 }
2640 }
2641 *maxIndexPtr = maxIndex;
2642 return minIndex;
2643}
2644
reed026beb52015-06-10 14:23:15 -07002645static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2646 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002647}
2648
reed@google.comac8543f2012-01-30 20:51:25 +00002649/*
2650 * We loop through all contours, and keep the computed cross-product of the
2651 * contour that contained the global y-max. If we just look at the first
2652 * contour, we may find one that is wound the opposite way (correctly) since
2653 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2654 * that is outer most (or at least has the global y-max) before we can consider
2655 * its cross product.
2656 */
reed026beb52015-06-10 14:23:15 -07002657bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002658 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2659 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002660 return true;
2661 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002662
2663 // don't want to pay the cost for computing this if it
2664 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002665 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2666 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002667 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002668 return false;
2669 }
reed@google.com69a99432012-01-10 18:00:10 +00002670
reed026beb52015-06-10 14:23:15 -07002671 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002672
reed@google.comac8543f2012-01-30 20:51:25 +00002673 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002674 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002675 SkScalar ymaxCross = 0;
2676
reed@google.com69a99432012-01-10 18:00:10 +00002677 for (; !iter.done(); iter.next()) {
2678 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002679 if (n < 3) {
2680 continue;
2681 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002682
reed@google.comcabaf1d2012-01-11 21:03:05 +00002683 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002684 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002685 int index = find_max_y(pts, n);
2686 if (pts[index].fY < ymax) {
2687 continue;
2688 }
2689
2690 // If there is more than 1 distinct point at the y-max, we take the
2691 // x-min and x-max of them and just subtract to compute the dir.
2692 if (pts[(index + 1) % n].fY == pts[index].fY) {
2693 int maxIndex;
2694 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2695 if (minIndex == maxIndex) {
2696 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002697 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002698 SkASSERT(pts[minIndex].fY == pts[index].fY);
2699 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2700 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2701 // we just subtract the indices, and let that auto-convert to
2702 // SkScalar, since we just want - or + to signal the direction.
2703 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002704 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002705 TRY_CROSSPROD:
2706 // Find a next and prev index to use for the cross-product test,
2707 // but we try to find pts that form non-zero vectors from pts[index]
2708 //
2709 // Its possible that we can't find two non-degenerate vectors, so
2710 // we have to guard our search (e.g. all the pts could be in the
2711 // same place).
2712
2713 // we pass n - 1 instead of -1 so we don't foul up % operator by
2714 // passing it a negative LH argument.
2715 int prev = find_diff_pt(pts, index, n, n - 1);
2716 if (prev == index) {
2717 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002718 continue;
2719 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002720 int next = find_diff_pt(pts, index, n, 1);
2721 SkASSERT(next != index);
2722 cross = cross_prod(pts[prev], pts[index], pts[next]);
2723 // if we get a zero and the points are horizontal, then we look at the spread in
2724 // x-direction. We really should continue to walk away from the degeneracy until
2725 // there is a divergence.
2726 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2727 // construct the subtract so we get the correct Direction below
2728 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002729 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002730 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002731
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002732 if (cross) {
2733 // record our best guess so far
2734 ymax = pts[index].fY;
2735 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002736 }
2737 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002738 if (ymaxCross) {
2739 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002740 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002741 return true;
2742 } else {
2743 return false;
2744 }
reed@google.comac8543f2012-01-30 20:51:25 +00002745}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002746
2747///////////////////////////////////////////////////////////////////////////////
2748
caryclark9aacd902015-12-14 08:38:09 -08002749static bool between(SkScalar a, SkScalar b, SkScalar c) {
2750 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2751 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2752 return (a - b) * (c - b) <= 0;
2753}
2754
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002755static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2756 SkScalar t) {
2757 SkScalar A = c3 + 3*(c1 - c2) - c0;
2758 SkScalar B = 3*(c2 - c1 - c1 + c0);
2759 SkScalar C = 3*(c1 - c0);
2760 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002761 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002762}
2763
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002764template <size_t N> static void find_minmax(const SkPoint pts[],
2765 SkScalar* minPtr, SkScalar* maxPtr) {
2766 SkScalar min, max;
2767 min = max = pts[0].fX;
2768 for (size_t i = 1; i < N; ++i) {
2769 min = SkMinScalar(min, pts[i].fX);
2770 max = SkMaxScalar(max, pts[i].fX);
2771 }
2772 *minPtr = min;
2773 *maxPtr = max;
2774}
2775
caryclark9cb5d752015-12-18 04:35:24 -08002776static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2777 if (start.fY == end.fY) {
2778 return between(start.fX, x, end.fX) && x != end.fX;
2779 } else {
2780 return x == start.fX && y == start.fY;
2781 }
2782}
2783
caryclark9aacd902015-12-14 08:38:09 -08002784static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002785 SkScalar y0 = pts[0].fY;
2786 SkScalar y3 = pts[3].fY;
2787
2788 int dir = 1;
2789 if (y0 > y3) {
2790 SkTSwap(y0, y3);
2791 dir = -1;
2792 }
2793 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002794 return 0;
2795 }
caryclark9cb5d752015-12-18 04:35:24 -08002796 if (checkOnCurve(x, y, pts[0], pts[3])) {
2797 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002798 return 0;
2799 }
caryclark9cb5d752015-12-18 04:35:24 -08002800 if (y == y3) {
2801 return 0;
2802 }
2803
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 // quickreject or quickaccept
2805 SkScalar min, max;
2806 find_minmax<4>(pts, &min, &max);
2807 if (x < min) {
2808 return 0;
2809 }
2810 if (x > max) {
2811 return dir;
2812 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002813
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002814 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002815 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002816 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002817 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002818 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002819 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002820 if (SkScalarNearlyEqual(xt, x)) {
2821 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2822 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002823 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002824 }
2825 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002826 return xt < x ? dir : 0;
2827}
2828
caryclark9aacd902015-12-14 08:38:09 -08002829static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002830 SkPoint dst[10];
2831 int n = SkChopCubicAtYExtrema(pts, dst);
2832 int w = 0;
2833 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002834 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002835 }
2836 return w;
2837}
2838
caryclark9aacd902015-12-14 08:38:09 -08002839static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2840 SkASSERT(src);
2841 SkASSERT(t >= 0 && t <= 1);
2842 SkScalar src2w = src[2] * w;
2843 SkScalar C = src[0];
2844 SkScalar A = src[4] - 2 * src2w + C;
2845 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002846 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002847}
2848
2849
2850static double conic_eval_denominator(SkScalar w, SkScalar t) {
2851 SkScalar B = 2 * (w - 1);
2852 SkScalar C = 1;
2853 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002854 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002855}
2856
2857static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2858 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002859 SkScalar y0 = pts[0].fY;
2860 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 int dir = 1;
2863 if (y0 > y2) {
2864 SkTSwap(y0, y2);
2865 dir = -1;
2866 }
caryclark9aacd902015-12-14 08:38:09 -08002867 if (y < y0 || y > y2) {
2868 return 0;
2869 }
caryclark9cb5d752015-12-18 04:35:24 -08002870 if (checkOnCurve(x, y, pts[0], pts[2])) {
2871 *onCurveCount += 1;
2872 return 0;
2873 }
caryclark9aacd902015-12-14 08:38:09 -08002874 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 return 0;
2876 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002877
caryclark9aacd902015-12-14 08:38:09 -08002878 SkScalar roots[2];
2879 SkScalar A = pts[2].fY;
2880 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2881 SkScalar C = pts[0].fY;
2882 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2883 B -= C; // B = b*w - w * yCept + yCept - a
2884 C -= y;
2885 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2886 SkASSERT(n <= 1);
2887 SkScalar xt;
2888 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002889 // zero roots are returned only when y0 == y
2890 // Need [0] if dir == 1
2891 // and [2] if dir == -1
2892 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002893 } else {
2894 SkScalar t = roots[0];
2895 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2896 }
2897 if (SkScalarNearlyEqual(xt, x)) {
2898 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2899 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002900 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002901 }
2902 }
2903 return xt < x ? dir : 0;
2904}
2905
2906static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2907 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2908 if (y0 == y1) {
2909 return true;
2910 }
2911 if (y0 < y1) {
2912 return y1 <= y2;
2913 } else {
2914 return y1 >= y2;
2915 }
2916}
2917
2918static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2919 int* onCurveCount) {
2920 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002921 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002922 // If the data points are very large, the conic may not be monotonic but may also
2923 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002924 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2925 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2926 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002927 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2928 }
2929 return w;
2930}
2931
2932static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2933 SkScalar y0 = pts[0].fY;
2934 SkScalar y2 = pts[2].fY;
2935
2936 int dir = 1;
2937 if (y0 > y2) {
2938 SkTSwap(y0, y2);
2939 dir = -1;
2940 }
2941 if (y < y0 || y > y2) {
2942 return 0;
2943 }
caryclark9cb5d752015-12-18 04:35:24 -08002944 if (checkOnCurve(x, y, pts[0], pts[2])) {
2945 *onCurveCount += 1;
2946 return 0;
2947 }
caryclark9aacd902015-12-14 08:38:09 -08002948 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002949 return 0;
2950 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 // bounds check on X (not required. is it faster?)
2952#if 0
2953 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2954 return 0;
2955 }
2956#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002957
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002958 SkScalar roots[2];
2959 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2960 2 * (pts[1].fY - pts[0].fY),
2961 pts[0].fY - y,
2962 roots);
2963 SkASSERT(n <= 1);
2964 SkScalar xt;
2965 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002966 // zero roots are returned only when y0 == y
2967 // Need [0] if dir == 1
2968 // and [2] if dir == -1
2969 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002970 } else {
2971 SkScalar t = roots[0];
2972 SkScalar C = pts[0].fX;
2973 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2974 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002975 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 }
caryclark9aacd902015-12-14 08:38:09 -08002977 if (SkScalarNearlyEqual(xt, x)) {
2978 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2979 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002980 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002981 }
2982 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 return xt < x ? dir : 0;
2984}
2985
caryclark9aacd902015-12-14 08:38:09 -08002986static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002987 SkPoint dst[5];
2988 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002989
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002990 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2991 n = SkChopQuadAtYExtrema(pts, dst);
2992 pts = dst;
2993 }
caryclark9aacd902015-12-14 08:38:09 -08002994 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08002996 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002997 }
2998 return w;
2999}
3000
caryclark9aacd902015-12-14 08:38:09 -08003001static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 SkScalar x0 = pts[0].fX;
3003 SkScalar y0 = pts[0].fY;
3004 SkScalar x1 = pts[1].fX;
3005 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003006
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003007 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003008
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003009 int dir = 1;
3010 if (y0 > y1) {
3011 SkTSwap(y0, y1);
3012 dir = -1;
3013 }
caryclark9aacd902015-12-14 08:38:09 -08003014 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 return 0;
3016 }
caryclark9cb5d752015-12-18 04:35:24 -08003017 if (checkOnCurve(x, y, pts[0], pts[1])) {
3018 *onCurveCount += 1;
3019 return 0;
3020 }
3021 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003022 return 0;
3023 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003024 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003025
caryclark9aacd902015-12-14 08:38:09 -08003026 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003027 // zero cross means the point is on the line, and since the case where
3028 // y of the query point is at the end point is handled above, we can be
3029 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003030 if (x != x1 || y != pts[1].fY) {
3031 *onCurveCount += 1;
3032 }
caryclark9aacd902015-12-14 08:38:09 -08003033 dir = 0;
3034 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003035 dir = 0;
3036 }
3037 return dir;
3038}
3039
caryclark9aacd902015-12-14 08:38:09 -08003040static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3041 SkTDArray<SkVector>* tangents) {
3042 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3043 && !between(pts[2].fY, y, pts[3].fY)) {
3044 return;
3045 }
3046 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3047 && !between(pts[2].fX, x, pts[3].fX)) {
3048 return;
3049 }
3050 SkPoint dst[10];
3051 int n = SkChopCubicAtYExtrema(pts, dst);
3052 for (int i = 0; i <= n; ++i) {
3053 SkPoint* c = &dst[i * 3];
3054 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003055 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003056 continue;
mbarbella276e6332016-05-31 14:44:01 -07003057 }
caryclark9aacd902015-12-14 08:38:09 -08003058 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3059 if (!SkScalarNearlyEqual(x, xt)) {
3060 continue;
3061 }
3062 SkVector tangent;
3063 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3064 tangents->push(tangent);
3065 }
3066}
3067
3068static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3069 SkTDArray<SkVector>* tangents) {
3070 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3071 return;
3072 }
3073 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3074 return;
3075 }
3076 SkScalar roots[2];
3077 SkScalar A = pts[2].fY;
3078 SkScalar B = pts[1].fY * w - y * w + y;
3079 SkScalar C = pts[0].fY;
3080 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3081 B -= C; // B = b*w - w * yCept + yCept - a
3082 C -= y;
3083 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3084 for (int index = 0; index < n; ++index) {
3085 SkScalar t = roots[index];
3086 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3087 if (!SkScalarNearlyEqual(x, xt)) {
3088 continue;
3089 }
3090 SkConic conic(pts, w);
3091 tangents->push(conic.evalTangentAt(t));
3092 }
3093}
3094
3095static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3096 SkTDArray<SkVector>* tangents) {
3097 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3098 return;
3099 }
3100 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3101 return;
3102 }
3103 SkScalar roots[2];
3104 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3105 2 * (pts[1].fY - pts[0].fY),
3106 pts[0].fY - y,
3107 roots);
3108 for (int index = 0; index < n; ++index) {
3109 SkScalar t = roots[index];
3110 SkScalar C = pts[0].fX;
3111 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3112 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003113 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003114 if (!SkScalarNearlyEqual(x, xt)) {
3115 continue;
3116 }
3117 tangents->push(SkEvalQuadTangentAt(pts, t));
3118 }
3119}
3120
3121static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3122 SkTDArray<SkVector>* tangents) {
3123 SkScalar y0 = pts[0].fY;
3124 SkScalar y1 = pts[1].fY;
3125 if (!between(y0, y, y1)) {
3126 return;
3127 }
3128 SkScalar x0 = pts[0].fX;
3129 SkScalar x1 = pts[1].fX;
3130 if (!between(x0, x, x1)) {
3131 return;
3132 }
3133 SkScalar dx = x1 - x0;
3134 SkScalar dy = y1 - y0;
3135 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3136 return;
3137 }
3138 SkVector v;
3139 v.set(dx, dy);
3140 tangents->push(v);
3141}
3142
reed@google.com4db592c2013-10-30 17:39:43 +00003143static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3144 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3145}
3146
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003147bool SkPath::contains(SkScalar x, SkScalar y) const {
3148 bool isInverse = this->isInverseFillType();
3149 if (this->isEmpty()) {
3150 return isInverse;
3151 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003152
reed@google.com4db592c2013-10-30 17:39:43 +00003153 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003154 return isInverse;
3155 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003156
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003157 SkPath::Iter iter(*this, true);
3158 bool done = false;
3159 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003160 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003161 do {
3162 SkPoint pts[4];
3163 switch (iter.next(pts, false)) {
3164 case SkPath::kMove_Verb:
3165 case SkPath::kClose_Verb:
3166 break;
3167 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003168 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003169 break;
3170 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003171 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003172 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003173 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003174 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003175 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003176 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003177 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003178 break;
3179 case SkPath::kDone_Verb:
3180 done = true;
3181 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003182 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003183 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003184 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3185 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3186 if (evenOddFill) {
3187 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003188 }
caryclark9aacd902015-12-14 08:38:09 -08003189 if (w) {
3190 return !isInverse;
3191 }
3192 if (onCurveCount <= 1) {
3193 return SkToBool(onCurveCount) ^ isInverse;
3194 }
3195 if ((onCurveCount & 1) || evenOddFill) {
3196 return SkToBool(onCurveCount & 1) ^ isInverse;
3197 }
halcanary9d524f22016-03-29 09:03:52 -07003198 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003199 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3200 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003201 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003202 SkTDArray<SkVector> tangents;
3203 do {
3204 SkPoint pts[4];
3205 int oldCount = tangents.count();
3206 switch (iter.next(pts, false)) {
3207 case SkPath::kMove_Verb:
3208 case SkPath::kClose_Verb:
3209 break;
3210 case SkPath::kLine_Verb:
3211 tangent_line(pts, x, y, &tangents);
3212 break;
3213 case SkPath::kQuad_Verb:
3214 tangent_quad(pts, x, y, &tangents);
3215 break;
3216 case SkPath::kConic_Verb:
3217 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3218 break;
3219 case SkPath::kCubic_Verb:
3220 tangent_cubic(pts, x, y, &tangents);
3221 break;
3222 case SkPath::kDone_Verb:
3223 done = true;
3224 break;
3225 }
3226 if (tangents.count() > oldCount) {
3227 int last = tangents.count() - 1;
3228 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003229 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003230 tangents.remove(last);
3231 } else {
3232 for (int index = 0; index < last; ++index) {
3233 const SkVector& test = tangents[index];
3234 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003235 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3236 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003237 tangents.remove(last);
3238 tangents.removeShuffle(index);
3239 break;
3240 }
3241 }
3242 }
3243 }
3244 } while (!done);
3245 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003246}
fmalitaaa0df4e2015-12-01 09:13:23 -08003247
3248int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3249 SkScalar w, SkPoint pts[], int pow2) {
3250 const SkConic conic(p0, p1, p2, w);
3251 return conic.chopIntoQuadsPOW2(pts, pow2);
3252}
bsalomonedc743a2016-06-01 09:42:31 -07003253
3254bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3255 unsigned* start) {
3256 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3257 return false;
3258 }
3259 SkPath::RawIter iter(path);
3260 SkPoint verbPts[4];
3261 SkPath::Verb v;
3262 SkPoint rectPts[5];
3263 int rectPtCnt = 0;
3264 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3265 switch (v) {
3266 case SkPath::kMove_Verb:
3267 if (0 != rectPtCnt) {
3268 return false;
3269 }
3270 rectPts[0] = verbPts[0];
3271 ++rectPtCnt;
3272 break;
3273 case SkPath::kLine_Verb:
3274 if (5 == rectPtCnt) {
3275 return false;
3276 }
3277 rectPts[rectPtCnt] = verbPts[1];
3278 ++rectPtCnt;
3279 break;
3280 case SkPath::kClose_Verb:
3281 if (4 == rectPtCnt) {
3282 rectPts[4] = rectPts[0];
3283 rectPtCnt = 5;
3284 }
3285 break;
3286 default:
3287 return false;
3288 }
3289 }
3290 if (rectPtCnt < 5) {
3291 return false;
3292 }
3293 if (rectPts[0] != rectPts[4]) {
3294 return false;
3295 }
bsalomon057ae8a2016-07-24 05:37:26 -07003296 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3297 // and pts 1 and 2 the opposite vertical or horizontal edge).
3298 bool vec03IsVertical;
3299 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3300 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3301 // Make sure it has non-zero width and height
3302 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003303 return false;
3304 }
bsalomon057ae8a2016-07-24 05:37:26 -07003305 vec03IsVertical = true;
3306 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3307 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3308 // Make sure it has non-zero width and height
3309 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3310 return false;
3311 }
3312 vec03IsVertical = false;
3313 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003314 return false;
3315 }
bsalomon057ae8a2016-07-24 05:37:26 -07003316 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3317 // set if it is on the bottom edge.
3318 unsigned sortFlags =
3319 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3320 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3321 switch (sortFlags) {
3322 case 0b00:
3323 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3324 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3325 *start = 0;
3326 break;
3327 case 0b01:
3328 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3329 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3330 *start = 1;
3331 break;
3332 case 0b10:
3333 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3334 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3335 *start = 3;
3336 break;
3337 case 0b11:
3338 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3339 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3340 *start = 2;
3341 break;
bsalomonedc743a2016-06-01 09:42:31 -07003342 }
bsalomonedc743a2016-06-01 09:42:31 -07003343 return true;
3344}
bsalomon21af9ca2016-08-25 12:29:23 -07003345
3346void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3347 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3348 SkASSERT(!oval.isEmpty());
3349 SkASSERT(sweepAngle);
3350
3351 path->reset();
3352 path->setIsVolatile(true);
3353 path->setFillType(SkPath::kWinding_FillType);
3354 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3355 path->addOval(oval);
3356 return;
3357 }
3358 if (useCenter) {
3359 path->moveTo(oval.centerX(), oval.centerY());
3360 }
3361 // Arc to mods at 360 and drawArc is not supposed to.
3362 bool forceMoveTo = !useCenter;
3363 while (sweepAngle <= -360.f) {
3364 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3365 startAngle -= 180.f;
3366 path->arcTo(oval, startAngle, -180.f, false);
3367 startAngle -= 180.f;
3368 forceMoveTo = false;
3369 sweepAngle += 360.f;
3370 }
3371 while (sweepAngle >= 360.f) {
3372 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3373 startAngle += 180.f;
3374 path->arcTo(oval, startAngle, 180.f, false);
3375 startAngle += 180.f;
3376 forceMoveTo = false;
3377 sweepAngle -= 360.f;
3378 }
3379 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3380 if (useCenter) {
3381 path->close();
3382 }
3383}
Mike Reed0d7dac82017-02-02 17:45:56 -08003384
3385///////////////////////////////////////////////////////////////////////////////////////////////////
3386#include "SkNx.h"
3387
3388static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3389 SkScalar ts[2];
3390 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3391 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3392 SkASSERT(n >= 0 && n <= 2);
3393 for (int i = 0; i < n; ++i) {
3394 extremas[i] = SkEvalQuadAt(src, ts[i]);
3395 }
3396 extremas[n] = src[2];
3397 return n + 1;
3398}
3399
3400static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3401 SkConic conic(src[0], src[1], src[2], w);
3402 SkScalar ts[2];
3403 int n = conic.findXExtrema(ts);
3404 n += conic.findYExtrema(&ts[n]);
3405 SkASSERT(n >= 0 && n <= 2);
3406 for (int i = 0; i < n; ++i) {
3407 extremas[i] = conic.evalAt(ts[i]);
3408 }
3409 extremas[n] = src[2];
3410 return n + 1;
3411}
3412
3413static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3414 SkScalar ts[4];
3415 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3416 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3417 SkASSERT(n >= 0 && n <= 4);
3418 for (int i = 0; i < n; ++i) {
3419 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3420 }
3421 extremas[n] = src[3];
3422 return n + 1;
3423}
3424
Mike Reed8d3196b2017-02-03 11:34:13 -05003425SkRect SkPath::computeTightBounds() const {
3426 if (0 == this->countVerbs()) {
3427 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003428 }
3429
Mike Reed8d3196b2017-02-03 11:34:13 -05003430 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3431 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003432 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003433
Mike Reed0d7dac82017-02-02 17:45:56 -08003434 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3435 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003436 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003437
3438 // initial with the first MoveTo, so we don't have to check inside the switch
3439 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003440 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003441 for (;;) {
3442 int count = 0;
3443 switch (iter.next(pts)) {
3444 case SkPath::kMove_Verb:
3445 extremas[0] = pts[0];
3446 count = 1;
3447 break;
3448 case SkPath::kLine_Verb:
3449 extremas[0] = pts[1];
3450 count = 1;
3451 break;
3452 case SkPath::kQuad_Verb:
3453 count = compute_quad_extremas(pts, extremas);
3454 break;
3455 case SkPath::kConic_Verb:
3456 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3457 break;
3458 case SkPath::kCubic_Verb:
3459 count = compute_cubic_extremas(pts, extremas);
3460 break;
3461 case SkPath::kClose_Verb:
3462 break;
3463 case SkPath::kDone_Verb:
3464 goto DONE;
3465 }
3466 for (int i = 0; i < count; ++i) {
3467 Sk2s tmp = from_point(extremas[i]);
3468 min = Sk2s::Min(min, tmp);
3469 max = Sk2s::Max(max, tmp);
3470 }
3471 }
3472DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003473 SkRect bounds;
3474 min.store((SkPoint*)&bounds.fLeft);
3475 max.store((SkPoint*)&bounds.fRight);
3476 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003477}
Cary Clarkdf429f32017-11-08 11:44:31 -05003478
3479bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3480 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3481}
3482
3483bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3484 const SkPoint& p3, bool exact) {
3485 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3486 SkPointPriv::EqualsWithinTolerance(p2, p3);
3487}
3488
3489bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3490 const SkPoint& p3, const SkPoint& p4, bool exact) {
3491 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3492 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3493 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3494 SkPointPriv::EqualsWithinTolerance(p3, p4);
3495}