blob: 0bfeae1d91f21a22c67358735cceaf62be9def2d [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
bsalomon1978ce22016-05-31 10:42:16 -07008#include <cmath>
djsollen@google.com94e75ee2012-06-08 18:30:46 +00009#include "SkBuffer.h"
caryclark9aacd902015-12-14 08:38:09 -080010#include "SkCubicClipper.h"
Mike Reed41a930f2017-07-26 17:33:44 -040011#include "SkData.h"
reed220f9262014-12-17 08:21:04 -080012#include "SkGeometry.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkMath.h"
Cary Clark9480d822017-10-19 18:01:13 -040014#include "SkMatrixPriv.h"
reed026beb52015-06-10 14:23:15 -070015#include "SkPathPriv.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkPathRef.h"
Cary Clarkdf429f32017-11-08 11:44:31 -050017#include "SkPointPriv.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000018#include "SkRRect.h"
Mike Reed267eccc2018-02-21 15:55:14 -050019#include "SkSafeMath.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000020
Mike Reeda99b6ce2017-02-04 11:04:26 -050021static float poly_eval(float A, float B, float C, float t) {
22 return (A * t + B) * t + C;
23}
24
25static float poly_eval(float A, float B, float C, float D, float t) {
26 return ((A * t + B) * t + C) * t + D;
27}
28
reed@android.com8a1c16f2008-12-17 15:59:43 +000029////////////////////////////////////////////////////////////////////////////
30
reed@google.com3563c9e2011-11-14 19:34:57 +000031/**
32 * Path.bounds is defined to be the bounds of all the control points.
33 * If we called bounds.join(r) we would skip r if r was empty, which breaks
34 * our promise. Hence we have a custom joiner that doesn't look at emptiness
35 */
36static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
37 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
38 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
39 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
40 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
41}
42
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000043static bool is_degenerate(const SkPath& path) {
44 SkPath::Iter iter(path, false);
45 SkPoint pts[4];
46 return SkPath::kDone_Verb == iter.next(pts);
47}
48
bsalomon@google.com30c174b2012-11-13 14:36:42 +000049class SkAutoDisableDirectionCheck {
50public:
51 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
herb9f4dbca2015-09-28 11:05:47 -070052 fSaved = static_cast<SkPathPriv::FirstDirection>(fPath->fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +000053 }
54
55 ~SkAutoDisableDirectionCheck() {
reed026beb52015-06-10 14:23:15 -070056 fPath->fFirstDirection = fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000057 }
58
59private:
reed026beb52015-06-10 14:23:15 -070060 SkPath* fPath;
61 SkPathPriv::FirstDirection fSaved;
bsalomon@google.com30c174b2012-11-13 14:36:42 +000062};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +000063#define SkAutoDisableDirectionCheck(...) SK_REQUIRE_LOCAL_VAR(SkAutoDisableDirectionCheck)
bsalomon@google.com30c174b2012-11-13 14:36:42 +000064
reed@android.com8a1c16f2008-12-17 15:59:43 +000065/* This guy's constructor/destructor bracket a path editing operation. It is
66 used when we know the bounds of the amount we are going to add to the path
67 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000068
reed@android.com8a1c16f2008-12-17 15:59:43 +000069 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000070 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000071 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000072
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000073 It also notes if the path was originally degenerate, and if so, sets
74 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.com466310d2013-12-03 16:43:54 +000075 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 */
77class SkAutoPathBoundsUpdate {
78public:
79 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
80 this->init(path);
81 }
82
83 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
84 SkScalar right, SkScalar bottom) {
85 fRect.set(left, top, right, bottom);
86 this->init(path);
87 }
reed@google.comabf15c12011-01-18 20:35:51 +000088
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +000090 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
91 : SkPath::kUnknown_Convexity);
Mike Reed926e1932018-01-29 15:56:11 -050092 if ((fEmpty || fHasValidBounds) && fRect.isFinite()) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +000093 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +000094 }
95 }
reed@google.comabf15c12011-01-18 20:35:51 +000096
reed@android.com8a1c16f2008-12-17 15:59:43 +000097private:
reed@android.com6b82d1a2009-06-03 02:35:01 +000098 SkPath* fPath;
99 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000100 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000101 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000102 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000103
reed@android.com6b82d1a2009-06-03 02:35:01 +0000104 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000105 // Cannot use fRect for our bounds unless we know it is sorted
106 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000108 // Mark the path's bounds as dirty if (1) they are, or (2) the path
109 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000111 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000112 if (fHasValidBounds && !fEmpty) {
113 joinNoEmptyChecks(&fRect, fPath->getBounds());
114 }
115 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000116 }
117};
commit-bot@chromium.orge61a86c2013-11-18 16:03:59 +0000118#define SkAutoPathBoundsUpdate(...) SK_REQUIRE_LOCAL_VAR(SkAutoPathBoundsUpdate)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119
reed@android.com8a1c16f2008-12-17 15:59:43 +0000120////////////////////////////////////////////////////////////////////////////
121
122/*
123 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000124 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000125 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
126
127 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000128 1. If we encounter degenerate segments, remove them
129 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
130 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
131 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132*/
133
134////////////////////////////////////////////////////////////////////////////
135
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000136// flag to require a moveTo if we begin with something else, like lineTo etc.
137#define INITIAL_LASTMOVETOINDEX_VALUE ~0
138
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000139SkPath::SkPath()
djsollen523cda32015-02-17 08:06:32 -0800140 : fPathRef(SkPathRef::CreateEmpty()) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000141 this->resetFields();
jvanverthb3eb6872014-10-24 07:12:51 -0700142 fIsVolatile = false;
Yuqian Li94387902018-04-11 16:34:06 -0400143 fIsBadForDAA = false;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000144}
145
146void SkPath::resetFields() {
147 //fPathRef is assumed to have been emptied by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000148 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000149 fFillType = kWinding_FillType;
reed@google.com04863fa2011-05-15 04:08:24 +0000150 fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -0700151 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000152
153 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
halcanary96fcdcc2015-08-27 07:41:13 -0700154 // don't want to muck with it if it's been set to something non-nullptr.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000155}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
bungeman@google.coma5809a32013-06-21 15:13:34 +0000157SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000158 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000159 this->copyFields(that);
160 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161}
162
163SkPath::~SkPath() {
164 SkDEBUGCODE(this->validate();)
165}
166
bungeman@google.coma5809a32013-06-21 15:13:34 +0000167SkPath& SkPath::operator=(const SkPath& that) {
168 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170 if (this != &that) {
171 fPathRef.reset(SkRef(that.fPathRef.get()));
172 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 }
174 SkDEBUGCODE(this->validate();)
175 return *this;
176}
177
bungeman@google.coma5809a32013-06-21 15:13:34 +0000178void SkPath::copyFields(const SkPath& that) {
179 //fPathRef is assumed to have been set by the caller.
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000180 fLastMoveToIndex = that.fLastMoveToIndex;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000181 fFillType = that.fFillType;
jvanverthb3eb6872014-10-24 07:12:51 -0700182 fIsVolatile = that.fIsVolatile;
Yuqian Li94387902018-04-11 16:34:06 -0400183 fIsBadForDAA = that.fIsBadForDAA;
Mike Kleinb9b5de52017-09-27 13:26:22 -0400184
185 // Non-atomic assignment of atomic values.
186 fConvexity .store(that.fConvexity .load());
187 fFirstDirection.store(that.fFirstDirection.load());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188}
189
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000190bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000191 // note: don't need to look at isConvex or bounds, since just comparing the
192 // raw data is sufficient.
reed@android.com3abec1d2009-03-02 05:36:20 +0000193 return &a == &b ||
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000194 (a.fFillType == b.fFillType && *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::swap(SkPath& that) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000198 if (this != &that) {
bungeman77a53de2015-10-01 12:28:49 -0700199 fPathRef.swap(that.fPathRef);
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000200 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201 SkTSwap<uint8_t>(fFillType, that.fFillType);
jvanverthb3eb6872014-10-24 07:12:51 -0700202 SkTSwap<SkBool8>(fIsVolatile, that.fIsVolatile);
Mike Kleinb9b5de52017-09-27 13:26:22 -0400203
204 // Non-atomic swaps of atomic values.
205 Convexity c = fConvexity.load();
206 fConvexity.store(that.fConvexity.load());
207 that.fConvexity.store(c);
208
209 uint8_t fd = fFirstDirection.load();
210 fFirstDirection.store(that.fFirstDirection.load());
211 that.fFirstDirection.store(fd);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000212 }
213}
214
caryclark8e7b19d2016-02-18 04:11:48 -0800215bool SkPath::isInterpolatable(const SkPath& compare) const {
216 int count = fPathRef->countVerbs();
217 if (count != compare.fPathRef->countVerbs()) {
218 return false;
219 }
220 if (!count) {
221 return true;
222 }
223 if (memcmp(fPathRef->verbsMemBegin(), compare.fPathRef->verbsMemBegin(),
224 count)) {
225 return false;
226 }
caryclarkdcd1fcc2016-02-18 09:00:01 -0800227 return !fPathRef->countWeights() ||
228 !SkToBool(memcmp(fPathRef->conicWeights(), compare.fPathRef->conicWeights(),
caryclark8e7b19d2016-02-18 04:11:48 -0800229 fPathRef->countWeights() * sizeof(*fPathRef->conicWeights())));
230}
231
232bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const {
Cary Clark7487ec82018-03-06 09:30:46 -0500233 int pointCount = fPathRef->countPoints();
234 if (pointCount != ending.fPathRef->countPoints()) {
caryclark8e7b19d2016-02-18 04:11:48 -0800235 return false;
236 }
Cary Clark7487ec82018-03-06 09:30:46 -0500237 if (!pointCount) {
caryclarka1105382016-02-18 06:13:25 -0800238 return true;
239 }
caryclark8e7b19d2016-02-18 04:11:48 -0800240 out->reset();
241 out->addPath(*this);
bungeman6bd52842016-10-27 09:30:08 -0700242 fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get());
caryclark8e7b19d2016-02-18 04:11:48 -0800243 return true;
244}
245
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000246static inline bool check_edge_against_rect(const SkPoint& p0,
247 const SkPoint& p1,
248 const SkRect& rect,
reed026beb52015-06-10 14:23:15 -0700249 SkPathPriv::FirstDirection dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000250 const SkPoint* edgeBegin;
251 SkVector v;
reed026beb52015-06-10 14:23:15 -0700252 if (SkPathPriv::kCW_FirstDirection == dir) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000253 v = p1 - p0;
254 edgeBegin = &p0;
255 } else {
256 v = p0 - p1;
257 edgeBegin = &p1;
258 }
259 if (v.fX || v.fY) {
260 // check the cross product of v with the vec from edgeBegin to each rect corner
Mike Reeda99b6ce2017-02-04 11:04:26 -0500261 SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX);
262 SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY);
263 SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX);
264 SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000265 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
266 return false;
267 }
268 }
269 return true;
270}
271
272bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
273 // This only handles non-degenerate convex paths currently.
274 if (kConvex_Convexity != this->getConvexity()) {
275 return false;
276 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000277
reed026beb52015-06-10 14:23:15 -0700278 SkPathPriv::FirstDirection direction;
279 if (!SkPathPriv::CheapComputeFirstDirection(*this, &direction)) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000280 return false;
281 }
282
283 SkPoint firstPt;
284 SkPoint prevPt;
Lee Salzmana19f0242017-01-12 13:06:21 -0500285 SkPath::Iter iter(*this, true);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000286 SkPath::Verb verb;
287 SkPoint pts[4];
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400288 int segmentCount = 0;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000289 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000290 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000291
Lee Salzmana19f0242017-01-12 13:06:21 -0500292 while ((verb = iter.next(pts, true, true)) != kDone_Verb) {
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 int nextPt = -1;
294 switch (verb) {
295 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000296 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000297 SkDEBUGCODE(++moveCnt);
298 firstPt = prevPt = pts[0];
299 break;
300 case kLine_Verb:
301 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000302 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400303 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000304 break;
305 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000306 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000307 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400308 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 nextPt = 2;
310 break;
311 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000312 SkASSERT(moveCnt && !closeCount);
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400313 ++segmentCount;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314 nextPt = 3;
315 break;
316 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000318 break;
319 default:
320 SkDEBUGFAIL("unknown verb");
321 }
322 if (-1 != nextPt) {
reed220f9262014-12-17 08:21:04 -0800323 if (SkPath::kConic_Verb == verb) {
324 SkConic orig;
325 orig.set(pts, iter.conicWeight());
326 SkPoint quadPts[5];
327 int count = orig.chopIntoQuadsPOW2(quadPts, 1);
djsollenf2b340f2016-01-29 08:51:04 -0800328 SkASSERT_RELEASE(2 == count);
reed220f9262014-12-17 08:21:04 -0800329
330 if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) {
331 return false;
332 }
333 if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) {
334 return false;
335 }
336 } else {
337 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
338 return false;
339 }
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
Brian Salomone9bf6dc2017-08-07 11:15:00 -0400345 if (segmentCount) {
346 return check_edge_against_rect(prevPt, firstPt, rect, direction);
347 }
348 return false;
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000349}
350
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000351uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000352 uint32_t genID = fPathRef->genID();
djsollen523cda32015-02-17 08:06:32 -0800353#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
Cary Clarkb2015442017-09-14 10:31:36 -0400354 SkASSERT((unsigned)fFillType < (1 << (32 - SkPathPriv::kPathRefGenIDBitCnt)));
355 genID |= static_cast<uint32_t>(fFillType) << SkPathPriv::kPathRefGenIDBitCnt;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000356#endif
357 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000358}
djsollen@google.come63793a2012-03-21 15:39:03 +0000359
reed@android.com8a1c16f2008-12-17 15:59:43 +0000360void SkPath::reset() {
361 SkDEBUGCODE(this->validate();)
362
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000363 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000364 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000365}
366
367void SkPath::rewind() {
368 SkDEBUGCODE(this->validate();)
369
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000370 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000371 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000372}
373
fsb1475b02016-01-20 09:51:07 -0800374bool SkPath::isLastContourClosed() const {
375 int verbCount = fPathRef->countVerbs();
376 if (0 == verbCount) {
377 return false;
378 }
379 return kClose_Verb == fPathRef->atVerb(verbCount - 1);
380}
381
reed@google.com7e6c4d12012-05-10 14:05:43 +0000382bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000383 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000385 if (2 == verbCount) {
386 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
387 if (kLine_Verb == fPathRef->atVerb(1)) {
388 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000389 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000390 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000391 line[0] = pts[0];
392 line[1] = pts[1];
393 }
394 return true;
395 }
396 }
397 return false;
398}
399
caryclark@google.comf1316942011-07-26 19:54:45 +0000400/*
401 Determines if path is a rect by keeping track of changes in direction
402 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000403
caryclark@google.comf1316942011-07-26 19:54:45 +0000404 The direction is computed such that:
405 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000406 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000407 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000408 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000409
caryclark@google.comf1316942011-07-26 19:54:45 +0000410A rectangle cycles up/right/down/left or up/left/down/right.
411
412The test fails if:
413 The path is closed, and followed by a line.
414 A second move creates a new endpoint.
415 A diagonal line is parsed.
416 There's more than four changes of direction.
417 There's a discontinuity on the line (e.g., a move in the middle)
418 The line reverses direction.
caryclark@google.comf1316942011-07-26 19:54:45 +0000419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000421 *The rectangle doesn't complete a cycle.
422 *The final point isn't equal to the first point.
423
424 *These last two conditions we relax if we have a 3-edge path that would
425 form a rectangle if it were closed (as we do when we fill a path)
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427It's OK if the path has:
428 Several colinear line segments composing a rectangle side.
429 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000430
caryclark@google.comf1316942011-07-26 19:54:45 +0000431The direction takes advantage of the corners found since opposite sides
432must travel in opposite directions.
433
434FIXME: Allow colinear quads and cubics to be treated like lines.
435FIXME: If the API passes fill-only, return true if the filled stroke
436 is a rectangle, though the caller failed to close the path.
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000437
Cary Clark48c464a2018-04-16 12:06:07 -0400438 directions values:
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000439 0x1 is set if the segment is horizontal
440 0x2 is set if the segment is moving to the right or down
441 thus:
442 two directions are opposites iff (dirA ^ dirB) == 0x2
443 two directions are perpendicular iff (dirA ^ dirB) == 0x1
skia.committer@gmail.comf5e67c12014-01-16 07:01:48 +0000444
caryclark@google.comf1316942011-07-26 19:54:45 +0000445 */
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000446static int rect_make_dir(SkScalar dx, SkScalar dy) {
447 return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1);
448}
Cary Clark88ba9712018-04-12 14:00:24 -0400449
caryclark@google.comf68154a2012-11-21 15:18:06 +0000450bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
Cary Clark5c715182018-04-09 16:07:11 -0400451 bool* isClosed, Direction* direction, SkRect* rect) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000452 int corners = 0;
Cary Clark1cd60982018-04-17 11:53:34 -0400453 SkPoint closeXY; // used to determine if final line falls on a diagonal
Cary Clark88ba9712018-04-12 14:00:24 -0400454 SkPoint lineStart; // used to construct line from previous point
Cary Clark8540e112018-04-11 14:30:27 -0400455 const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves)
456 const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed)
Cary Clarka7651562018-04-17 09:30:14 -0400457 const SkPoint* lastCountedPt = nullptr; // point creating 3rd corner
caryclark@google.com56f233a2012-11-19 13:06:06 +0000458 const SkPoint* pts = *ptsPtr;
Cary Clark8540e112018-04-11 14:30:27 -0400459 const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects
Cary Clark88ba9712018-04-12 14:00:24 -0400460 lineStart.set(0, 0);
Cary Clark48c464a2018-04-16 12:06:07 -0400461 signed char directions[] = {-1, -1, -1, -1}; // -1 to 3; -1 is uninitialized
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000462 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) {
Cary Clarka7651562018-04-17 09:30:14 -0400478 lastCountedPt = pts;
479 }
480 if (kClose_Verb != verb) {
Cary Clark8540e112018-04-11 14:30:27 -0400481 lastPt = pts;
482 }
Cary Clark88ba9712018-04-12 14:00:24 -0400483 SkPoint lineEnd = kClose_Verb == verb ? *firstPt : *pts++;
484 SkVector lineDelta = lineEnd - lineStart;
485 if (lineDelta.fX && lineDelta.fY) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000486 return false; // diagonal
487 }
Cary Clark88ba9712018-04-12 14:00:24 -0400488 if (lineStart == lineEnd) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 break; // single point on side OK
490 }
Cary Clarkd4228472018-04-13 07:07:04 -0400491 addedLine = true;
Cary Clark48c464a2018-04-16 12:06:07 -0400492 int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 if (0 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400494 directions[0] = nextDirection;
Cary Clark88ba9712018-04-12 14:00:24 -0400495 lineStart = lineEnd;
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 corners = 1;
497 closedOrMoved = false;
498 break;
499 }
500 if (closedOrMoved) {
501 return false; // closed followed by a line
502 }
Cary Clark48c464a2018-04-16 12:06:07 -0400503 if (autoClose && nextDirection == directions[0]) {
caryclark@google.combfe90372012-11-21 13:56:20 +0000504 break; // colinear with first
505 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000506 closedOrMoved = autoClose;
Cary Clark88ba9712018-04-12 14:00:24 -0400507 lineStart = lineEnd;
Cary Clark48c464a2018-04-16 12:06:07 -0400508 if (directions[corners - 1] == nextDirection) {
Cary Clarkb120e922018-04-18 12:25:08 -0400509 if (3 == corners && kLine_Verb == verb) {
510 lastCountedPt = lastPt;
511 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000512 break; // colinear segment
513 }
Cary Clark48c464a2018-04-16 12:06:07 -0400514 if (corners >= 4) {
515 return false; // too many direction changes
516 }
517 directions[corners++] = nextDirection;
518 // opposite lines must point in opposite directions; xoring them should equal 2
Cary Clarkb120e922018-04-18 12:25:08 -0400519 if (3 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400520 if ((directions[0] ^ directions[2]) != 2) {
521 return false;
522 }
Cary Clarka7651562018-04-17 09:30:14 -0400523 accumulatingRect = false;
Cary Clarkb120e922018-04-18 12:25:08 -0400524 } else if (4 == corners) {
Cary Clark48c464a2018-04-16 12:06:07 -0400525 if ((directions[1] ^ directions[3]) != 2) {
526 return false;
527 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000528 }
529 break;
530 }
531 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000532 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000533 case kCubic_Verb:
534 return false; // quadratic, cubic not allowed
535 case kMove_Verb:
Cary Clark48c464a2018-04-16 12:06:07 -0400536 if (allowPartial && !autoClose && directions[0] >= 0) {
caryclark95bc5f32015-04-08 08:34:15 -0700537 insertClose = true;
538 *currVerb -= 1; // try move again afterwards
539 goto addMissingClose;
540 }
Cary Clark8540e112018-04-11 14:30:27 -0400541 if (!addedLine) {
542 firstPt = pts;
543 accumulatingRect = true;
544 } else {
Cary Clark1cd60982018-04-17 11:53:34 -0400545 closeXY = *firstPt - *lastPt;
546 if (closeXY.fX && closeXY.fY) {
547 return false; // we're diagonal, abort
548 }
Cary Clark8540e112018-04-11 14:30:27 -0400549 accumulatingRect = false;
550 }
Cary Clark88ba9712018-04-12 14:00:24 -0400551 lineStart = *pts++;
caryclark@google.comf1316942011-07-26 19:54:45 +0000552 closedOrMoved = true;
553 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000554 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000555 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000556 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000557 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000558 *currVerb += 1;
caryclark95bc5f32015-04-08 08:34:15 -0700559addMissingClose:
560 ;
caryclark@google.comf1316942011-07-26 19:54:45 +0000561 }
562 // Success if 4 corners and first point equals last
Cary Clark8540e112018-04-11 14:30:27 -0400563 if (corners < 3 || corners > 4) {
564 return false;
565 }
Cary Clark1cd60982018-04-17 11:53:34 -0400566 closeXY = *firstPt - *lastPt;
Cary Clark58627cb2018-04-10 09:16:41 -0400567 // If autoClose, check if close generates diagonal
Cary Clark8540e112018-04-11 14:30:27 -0400568 bool result = 4 == corners && (closeXY.isZero() || (autoClose && (!closeXY.fX || !closeXY.fY)));
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000569 if (!result) {
Cary Clark4eb6f7a2018-04-17 13:34:37 -0400570 if (closeXY.fX && closeXY.fY) {
571 return false; // we're diagonal, abort
572 }
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000573 // check if we are just an incomplete rectangle, in which case we can
574 // return true, but not claim to be closed.
575 // e.g.
576 // 3 sided rectangle
577 // 4 sided but the last edge is not long enough to reach the start
578 //
Cary Clark8540e112018-04-11 14:30:27 -0400579 int closeDirection = rect_make_dir(closeXY.fX, closeXY.fY);
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000580 // make sure the close-segment doesn't double-back on itself
Cary Clarkb120e922018-04-18 12:25:08 -0400581 if (3 == corners || 2 == (closeDirection ^ directions[1])) {
commit-bot@chromium.org05ec2232014-01-15 18:00:57 +0000582 result = true;
583 autoClose = false; // we are not closed
584 }
585 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000586 if (savePts) {
587 *ptsPtr = savePts;
588 }
Cary Clark5c715182018-04-09 16:07:11 -0400589 if (result && rect) {
Cary Clarka7651562018-04-17 09:30:14 -0400590 ptrdiff_t count = lastCountedPt - firstPt + 1;
Cary Clark5c715182018-04-09 16:07:11 -0400591 rect->set(firstPt, (int) count);
592 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000593 if (result && isClosed) {
594 *isClosed = autoClose;
595 }
596 if (result && direction) {
Cary Clark48c464a2018-04-16 12:06:07 -0400597 *direction = directions[0] == ((directions[1] + 1) & 3) ? kCW_Direction : kCCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000598 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000599 return result;
600}
601
robertphillips4f662e62014-12-29 14:06:51 -0800602bool SkPath::isRect(SkRect* rect, bool* isClosed, Direction* direction) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000603 SkDEBUGCODE(this->validate();)
604 int currVerb = 0;
605 const SkPoint* pts = fPathRef->points();
Cary Clark5c715182018-04-09 16:07:11 -0400606 return this->isRectContour(false, &currVerb, &pts, isClosed, direction, rect);
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000607}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000608
caryclark95bc5f32015-04-08 08:34:15 -0700609bool SkPath::isNestedFillRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 SkDEBUGCODE(this->validate();)
611 int currVerb = 0;
612 const SkPoint* pts = fPathRef->points();
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000613 Direction testDirs[2];
Cary Clark5c715182018-04-09 16:07:11 -0400614 SkRect testRects[2];
615 if (!isRectContour(true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000616 return false;
617 }
Cary Clark5c715182018-04-09 16:07:11 -0400618 if (isRectContour(false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000619 if (testRects[0].contains(testRects[1])) {
620 if (rects) {
621 rects[0] = testRects[0];
622 rects[1] = testRects[1];
623 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000624 if (dirs) {
625 dirs[0] = testDirs[0];
626 dirs[1] = testDirs[1];
627 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000628 return true;
629 }
630 if (testRects[1].contains(testRects[0])) {
631 if (rects) {
632 rects[0] = testRects[1];
633 rects[1] = testRects[0];
634 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000635 if (dirs) {
636 dirs[0] = testDirs[1];
637 dirs[1] = testDirs[0];
638 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000639 return true;
640 }
641 }
642 return false;
643}
644
Mike Reed0c3137c2018-02-20 13:57:05 -0500645bool SkPath::isOval(SkRect* bounds) const {
646 return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr);
647}
648
649bool SkPath::isRRect(SkRRect* rrect) const {
650 return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr);
651}
652
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000653int SkPath::countPoints() const {
654 return fPathRef->countPoints();
655}
656
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000657int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000658 SkDEBUGCODE(this->validate();)
659
660 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000661 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 int count = SkMin32(max, fPathRef->countPoints());
mtklein8bf5d172015-12-09 13:15:07 -0800663 sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665}
666
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000667SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000668 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
669 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000670 }
671 return SkPoint::Make(0, 0);
672}
673
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000674int SkPath::countVerbs() const {
675 return fPathRef->countVerbs();
676}
677
678static inline void copy_verbs_reverse(uint8_t* inorderDst,
679 const uint8_t* reversedSrc,
680 int count) {
681 for (int i = 0; i < count; ++i) {
682 inorderDst[i] = reversedSrc[~i];
683 }
684}
685
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000686int SkPath::getVerbs(uint8_t dst[], int max) const {
687 SkDEBUGCODE(this->validate();)
688
689 SkASSERT(max >= 0);
690 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000691 int count = SkMin32(max, fPathRef->countVerbs());
692 copy_verbs_reverse(dst, fPathRef->verbs(), count);
693 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000694}
695
reed@google.com294dd7b2011-10-11 11:58:32 +0000696bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 SkDEBUGCODE(this->validate();)
698
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000699 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000700 if (count > 0) {
701 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000702 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000704 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000705 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000706 if (lastPt) {
707 lastPt->set(0, 0);
708 }
709 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710}
711
caryclarkaec25102015-04-29 08:28:30 -0700712void SkPath::setPt(int index, SkScalar x, SkScalar y) {
713 SkDEBUGCODE(this->validate();)
714
715 int count = fPathRef->countPoints();
716 if (count <= index) {
717 return;
718 } else {
719 SkPathRef::Editor ed(&fPathRef);
720 ed.atPoint(index)->set(x, y);
721 }
722}
723
reed@android.com8a1c16f2008-12-17 15:59:43 +0000724void SkPath::setLastPt(SkScalar x, SkScalar y) {
725 SkDEBUGCODE(this->validate();)
726
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000727 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000728 if (count == 0) {
729 this->moveTo(x, y);
730 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000731 SkPathRef::Editor ed(&fPathRef);
732 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733 }
734}
735
reed@google.com04863fa2011-05-15 04:08:24 +0000736void SkPath::setConvexity(Convexity c) {
737 if (fConvexity != c) {
738 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000739 }
740}
741
reed@android.com8a1c16f2008-12-17 15:59:43 +0000742//////////////////////////////////////////////////////////////////////////////
743// Construction methods
744
reed026beb52015-06-10 14:23:15 -0700745#define DIRTY_AFTER_EDIT \
746 do { \
747 fConvexity = kUnknown_Convexity; \
748 fFirstDirection = SkPathPriv::kUnknown_FirstDirection; \
reed@google.comb54455e2011-05-16 14:16:04 +0000749 } while (0)
750
reed@android.com8a1c16f2008-12-17 15:59:43 +0000751void SkPath::incReserve(U16CPU inc) {
752 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000753 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000754 SkDEBUGCODE(this->validate();)
755}
756
757void SkPath::moveTo(SkScalar x, SkScalar y) {
758 SkDEBUGCODE(this->validate();)
759
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000760 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000761
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000762 // remember our index
763 fLastMoveToIndex = fPathRef->countPoints();
764
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000765 ed.growForVerb(kMove_Verb)->set(x, y);
bsalomonb17c1292014-08-28 14:04:55 -0700766
767 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000768}
769
770void SkPath::rMoveTo(SkScalar x, SkScalar y) {
771 SkPoint pt;
772 this->getLastPt(&pt);
773 this->moveTo(pt.fX + x, pt.fY + y);
774}
775
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000776void SkPath::injectMoveToIfNeeded() {
777 if (fLastMoveToIndex < 0) {
778 SkScalar x, y;
779 if (fPathRef->countVerbs() == 0) {
780 x = y = 0;
781 } else {
782 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
783 x = pt.fX;
784 y = pt.fY;
785 }
786 this->moveTo(x, y);
787 }
788}
789
reed@android.com8a1c16f2008-12-17 15:59:43 +0000790void SkPath::lineTo(SkScalar x, SkScalar y) {
791 SkDEBUGCODE(this->validate();)
792
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000793 this->injectMoveToIfNeeded();
794
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000795 SkPathRef::Editor ed(&fPathRef);
796 ed.growForVerb(kLine_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797
reed@google.comb54455e2011-05-16 14:16:04 +0000798 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000799}
800
801void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000802 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 SkPoint pt;
804 this->getLastPt(&pt);
805 this->lineTo(pt.fX + x, pt.fY + y);
806}
807
808void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
809 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000810
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000811 this->injectMoveToIfNeeded();
812
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000813 SkPathRef::Editor ed(&fPathRef);
814 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000815 pts[0].set(x1, y1);
816 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000817
reed@google.comb54455e2011-05-16 14:16:04 +0000818 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819}
820
821void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000822 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000823 SkPoint pt;
824 this->getLastPt(&pt);
825 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
826}
827
reed@google.com277c3f82013-05-31 15:17:50 +0000828void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
829 SkScalar w) {
830 // check for <= 0 or NaN with this test
831 if (!(w > 0)) {
832 this->lineTo(x2, y2);
833 } else if (!SkScalarIsFinite(w)) {
834 this->lineTo(x1, y1);
835 this->lineTo(x2, y2);
836 } else if (SK_Scalar1 == w) {
837 this->quadTo(x1, y1, x2, y2);
838 } else {
839 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000840
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000841 this->injectMoveToIfNeeded();
842
reed@google.com277c3f82013-05-31 15:17:50 +0000843 SkPathRef::Editor ed(&fPathRef);
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +0000844 SkPoint* pts = ed.growForVerb(kConic_Verb, w);
reed@google.com277c3f82013-05-31 15:17:50 +0000845 pts[0].set(x1, y1);
846 pts[1].set(x2, y2);
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000847
reed@google.com277c3f82013-05-31 15:17:50 +0000848 DIRTY_AFTER_EDIT;
849 }
850}
851
852void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
853 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000854 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000855 SkPoint pt;
856 this->getLastPt(&pt);
857 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
858}
859
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
861 SkScalar x3, SkScalar y3) {
862 SkDEBUGCODE(this->validate();)
863
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000864 this->injectMoveToIfNeeded();
865
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000866 SkPathRef::Editor ed(&fPathRef);
867 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000868 pts[0].set(x1, y1);
869 pts[1].set(x2, y2);
870 pts[2].set(x3, y3);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871
reed@google.comb54455e2011-05-16 14:16:04 +0000872 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000873}
874
875void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
876 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000877 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878 SkPoint pt;
879 this->getLastPt(&pt);
880 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
881 pt.fX + x3, pt.fY + y3);
882}
883
884void SkPath::close() {
885 SkDEBUGCODE(this->validate();)
886
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000888 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000889 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 case kLine_Verb:
891 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000892 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000893 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000894 case kMove_Verb: {
895 SkPathRef::Editor ed(&fPathRef);
896 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000897 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000898 }
reed@google.com277c3f82013-05-31 15:17:50 +0000899 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000900 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000901 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000902 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000903 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000904 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000905 }
906 }
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +0000907
908 // signal that we need a moveTo to follow us (unless we're done)
909#if 0
910 if (fLastMoveToIndex >= 0) {
911 fLastMoveToIndex = ~fLastMoveToIndex;
912 }
913#else
914 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
915#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000916}
917
918///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000919
fmalitac08d53e2015-11-17 09:53:29 -0800920namespace {
921
922template <unsigned N>
923class PointIterator {
924public:
925 PointIterator(SkPath::Direction dir, unsigned startIndex)
926 : fCurrent(startIndex % N)
927 , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
928
929 const SkPoint& current() const {
930 SkASSERT(fCurrent < N);
931 return fPts[fCurrent];
932 }
933
934 const SkPoint& next() {
935 fCurrent = (fCurrent + fAdvance) % N;
936 return this->current();
937 }
938
939protected:
940 SkPoint fPts[N];
941
942private:
943 unsigned fCurrent;
944 unsigned fAdvance;
945};
946
947class RectPointIterator : public PointIterator<4> {
948public:
949 RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
950 : PointIterator(dir, startIndex) {
951
952 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
953 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
954 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
955 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
956 }
957};
958
959class OvalPointIterator : public PointIterator<4> {
960public:
961 OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
962 : PointIterator(dir, startIndex) {
963
964 const SkScalar cx = oval.centerX();
965 const SkScalar cy = oval.centerY();
966
967 fPts[0] = SkPoint::Make(cx, oval.fTop);
968 fPts[1] = SkPoint::Make(oval.fRight, cy);
969 fPts[2] = SkPoint::Make(cx, oval.fBottom);
970 fPts[3] = SkPoint::Make(oval.fLeft, cy);
971 }
972};
973
974class RRectPointIterator : public PointIterator<8> {
975public:
976 RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
977 : PointIterator(dir, startIndex) {
978
979 const SkRect& bounds = rrect.getBounds();
980 const SkScalar L = bounds.fLeft;
981 const SkScalar T = bounds.fTop;
982 const SkScalar R = bounds.fRight;
983 const SkScalar B = bounds.fBottom;
984
985 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
986 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
987 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
988 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
989 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
990 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
991 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
992 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
993 }
994};
995
996} // anonymous namespace
997
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000998static void assert_known_direction(int dir) {
999 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
1000}
1001
reed@android.com8a1c16f2008-12-17 15:59:43 +00001002void SkPath::addRect(const SkRect& rect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001003 this->addRect(rect, dir, 0);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001004}
1005
1006void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
1007 SkScalar bottom, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001008 this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
1009}
1010
1011void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001012 assert_known_direction(dir);
reed026beb52015-06-10 14:23:15 -07001013 fFirstDirection = this->hasOnlyMoveTos() ?
fmalitac08d53e2015-11-17 09:53:29 -08001014 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001015 SkAutoDisableDirectionCheck addc(this);
fmalitac08d53e2015-11-17 09:53:29 -08001016 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001017
fmalitac08d53e2015-11-17 09:53:29 -08001018 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001019
fmalitac08d53e2015-11-17 09:53:29 -08001020 const int kVerbs = 5; // moveTo + 3x lineTo + close
1021 this->incReserve(kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001022
fmalitac08d53e2015-11-17 09:53:29 -08001023 RectPointIterator iter(rect, dir, startIndex);
1024
1025 this->moveTo(iter.current());
1026 this->lineTo(iter.next());
1027 this->lineTo(iter.next());
1028 this->lineTo(iter.next());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001029 this->close();
fmalitac08d53e2015-11-17 09:53:29 -08001030
1031 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001032}
1033
reed@google.com744faba2012-05-29 19:54:52 +00001034void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
1035 SkDEBUGCODE(this->validate();)
1036 if (count <= 0) {
1037 return;
1038 }
1039
commit-bot@chromium.org5e1a7f22014-02-12 17:44:30 +00001040 fLastMoveToIndex = fPathRef->countPoints();
1041
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001042 // +close makes room for the extra kClose_Verb
1043 SkPathRef::Editor ed(&fPathRef, count+close, count);
1044
1045 ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY);
reed@google.com744faba2012-05-29 19:54:52 +00001046 if (count > 1) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001047 SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1);
1048 memcpy(p, &pts[1], (count-1) * sizeof(SkPoint));
reed@google.com744faba2012-05-29 19:54:52 +00001049 }
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001050
reed@google.com744faba2012-05-29 19:54:52 +00001051 if (close) {
robertphillips@google.com6b8dbb62013-12-12 23:03:51 +00001052 ed.growForVerb(kClose_Verb);
caryclark63c684a2015-02-25 09:04:04 -08001053 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
reed@google.com744faba2012-05-29 19:54:52 +00001054 }
1055
reed@google.com744faba2012-05-29 19:54:52 +00001056 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001057 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +00001058}
1059
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001060#include "SkGeometry.h"
1061
reedf90ea012015-01-29 12:03:58 -08001062static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1063 SkPoint* pt) {
1064 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001065 // Chrome uses this path to move into and out of ovals. If not
1066 // treated as a special case the moves can distort the oval's
1067 // bounding box (and break the circle special case).
reedf90ea012015-01-29 12:03:58 -08001068 pt->set(oval.fRight, oval.centerY());
1069 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001070 } else if (0 == oval.width() && 0 == oval.height()) {
1071 // Chrome will sometimes create 0 radius round rects. Having degenerate
1072 // quad segments in the path prevents the path from being recognized as
1073 // a rect.
1074 // TODO: optimizing the case where only one of width or height is zero
1075 // should also be considered. This case, however, doesn't seem to be
1076 // as common as the single point case.
reedf90ea012015-01-29 12:03:58 -08001077 pt->set(oval.fRight, oval.fTop);
1078 return true;
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001079 }
reedf90ea012015-01-29 12:03:58 -08001080 return false;
1081}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001082
reedd5d27d92015-02-09 13:54:43 -08001083// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
1084//
1085static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
1086 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
1087 startV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &startV->fX);
1088 stopV->fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle), &stopV->fX);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001089
1090 /* If the sweep angle is nearly (but less than) 360, then due to precision
reedd5d27d92015-02-09 13:54:43 -08001091 loss in radians-conversion and/or sin/cos, we may end up with coincident
1092 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1093 of drawing a nearly complete circle (good).
1094 e.g. canvas.drawArc(0, 359.99, ...)
1095 -vs- canvas.drawArc(0, 359.9, ...)
1096 We try to detect this edge case, and tweak the stop vector
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001097 */
reedd5d27d92015-02-09 13:54:43 -08001098 if (*startV == *stopV) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001099 SkScalar sw = SkScalarAbs(sweepAngle);
1100 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1101 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1102 // make a guess at a tiny angle (in radians) to tweak by
1103 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1104 // not sure how much will be enough, so we use a loop
1105 do {
1106 stopRad -= deltaRad;
reedd5d27d92015-02-09 13:54:43 -08001107 stopV->fY = SkScalarSinCos(stopRad, &stopV->fX);
1108 } while (*startV == *stopV);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001109 }
1110 }
reedd5d27d92015-02-09 13:54:43 -08001111 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
1112}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001113
reed9e779d42015-02-17 11:43:14 -08001114/**
1115 * If this returns 0, then the caller should just line-to the singlePt, else it should
1116 * ignore singlePt and append the specified number of conics.
1117 */
reedd5d27d92015-02-09 13:54:43 -08001118static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
reed9e779d42015-02-17 11:43:14 -08001119 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
1120 SkPoint* singlePt) {
reedd5d27d92015-02-09 13:54:43 -08001121 SkMatrix matrix;
1122
1123 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1124 matrix.postTranslate(oval.centerX(), oval.centerY());
1125
reed9e779d42015-02-17 11:43:14 -08001126 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
1127 if (0 == count) {
xidachen95e34a32016-10-19 10:24:28 -07001128 matrix.mapXY(stop.x(), stop.y(), singlePt);
reed9e779d42015-02-17 11:43:14 -08001129 }
1130 return count;
reedd5d27d92015-02-09 13:54:43 -08001131}
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001132
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001133void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001134 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001135 SkRRect rrect;
1136 rrect.setRectRadii(rect, (const SkVector*) radii);
1137 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001138}
1139
reed@google.com4ed0fb72012-12-12 20:48:18 +00001140void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001141 // legacy start indices: 6 (CW) and 7(CCW)
1142 this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
1143}
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001144
fmalitac08d53e2015-11-17 09:53:29 -08001145void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
1146 assert_known_direction(dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001147
caryclarkda707bf2015-11-19 14:47:43 -08001148 bool isRRect = hasOnlyMoveTos();
fmalitac08d53e2015-11-17 09:53:29 -08001149 const SkRect& bounds = rrect.getBounds();
1150
Brian Salomon0a241ce2017-12-15 11:31:05 -05001151 if (rrect.isRect() || rrect.isEmpty()) {
fmalitac08d53e2015-11-17 09:53:29 -08001152 // degenerate(rect) => radii points are collapsing
1153 this->addRect(bounds, dir, (startIndex + 1) / 2);
1154 } else if (rrect.isOval()) {
1155 // degenerate(oval) => line points are collapsing
1156 this->addOval(bounds, dir, startIndex / 2);
1157 } else {
1158 fFirstDirection = this->hasOnlyMoveTos() ?
1159 (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
1160
1161 SkAutoPathBoundsUpdate apbu(this, bounds);
1162 SkAutoDisableDirectionCheck addc(this);
1163
1164 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
1165 const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
1166 const SkScalar weight = SK_ScalarRoot2Over2;
1167
1168 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1169 const int kVerbs = startsWithConic
1170 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
1171 : 10; // moveTo + 4x lineTo + 4x conicTo + close
1172 this->incReserve(kVerbs);
1173
1174 RRectPointIterator rrectIter(rrect, dir, startIndex);
1175 // Corner iterator indices follow the collapsed radii model,
1176 // adjusted such that the start pt is "behind" the radii start pt.
1177 const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
1178 RectPointIterator rectIter(bounds, dir, rectStartIndex);
1179
1180 this->moveTo(rrectIter.current());
1181 if (startsWithConic) {
1182 for (unsigned i = 0; i < 3; ++i) {
1183 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1184 this->lineTo(rrectIter.next());
1185 }
1186 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1187 // final lineTo handled by close().
1188 } else {
1189 for (unsigned i = 0; i < 4; ++i) {
1190 this->lineTo(rrectIter.next());
1191 this->conicTo(rectIter.next(), rrectIter.next(), weight);
1192 }
1193 }
1194 this->close();
1195
caryclarkda707bf2015-11-19 14:47:43 -08001196 SkPathRef::Editor ed(&fPathRef);
bsalomon78d58d12016-05-27 09:17:04 -07001197 ed.setIsRRect(isRRect, dir, startIndex % 8);
caryclarkda707bf2015-11-19 14:47:43 -08001198
fmalitac08d53e2015-11-17 09:53:29 -08001199 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1200 }
1201
1202 SkDEBUGCODE(fPathRef->validate();)
reed@google.com4ed0fb72012-12-12 20:48:18 +00001203}
1204
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001205bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001206 int count = fPathRef->countVerbs();
1207 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1208 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209 if (*verbs == kLine_Verb ||
1210 *verbs == kQuad_Verb ||
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001211 *verbs == kConic_Verb ||
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001212 *verbs == kCubic_Verb) {
1213 return false;
1214 }
1215 ++verbs;
1216 }
1217 return true;
1218}
1219
Brian Osmana2318572017-07-10 15:09:26 -04001220bool SkPath::isZeroLengthSincePoint(int startPtIndex) const {
1221 int count = fPathRef->countPoints() - startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001222 if (count < 2) {
1223 return true;
1224 }
Brian Osmana2318572017-07-10 15:09:26 -04001225 const SkPoint* pts = fPathRef.get()->points() + startPtIndex;
caryclarkd49a86a2016-02-22 12:44:54 -08001226 const SkPoint& first = *pts;
1227 for (int index = 1; index < count; ++index) {
1228 if (first != pts[index]) {
1229 return false;
1230 }
1231 }
1232 return true;
1233}
1234
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001235void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1236 Direction dir) {
1237 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001238
humper@google.com75e3ca12013-04-08 21:44:11 +00001239 if (rx < 0 || ry < 0) {
humper@google.com75e3ca12013-04-08 21:44:11 +00001240 return;
1241 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001242
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001243 SkRRect rrect;
1244 rrect.setRectXY(rect, rx, ry);
1245 this->addRRect(rrect, dir);
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001246}
1247
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248void SkPath::addOval(const SkRect& oval, Direction dir) {
fmalitac08d53e2015-11-17 09:53:29 -08001249 // legacy start index: 1
1250 this->addOval(oval, dir, 1);
1251}
1252
1253void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001254 assert_known_direction(dir);
1255
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001256 /* If addOval() is called after previous moveTo(),
1257 this path is still marked as an oval. This is used to
1258 fit into WebKit's calling sequences.
1259 We can't simply check isEmpty() in this case, as additional
1260 moveTo() would mark the path non empty.
1261 */
robertphillips@google.com466310d2013-12-03 16:43:54 +00001262 bool isOval = hasOnlyMoveTos();
1263 if (isOval) {
reed026beb52015-06-10 14:23:15 -07001264 fFirstDirection = (SkPathPriv::FirstDirection)dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001265 } else {
reed026beb52015-06-10 14:23:15 -07001266 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001267 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001268
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001269 SkAutoDisableDirectionCheck addc(this);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 SkAutoPathBoundsUpdate apbu(this, oval);
1271
fmalitac08d53e2015-11-17 09:53:29 -08001272 SkDEBUGCODE(int initialVerbCount = this->countVerbs());
1273 const int kVerbs = 6; // moveTo + 4x conicTo + close
1274 this->incReserve(kVerbs);
1275
1276 OvalPointIterator ovalIter(oval, dir, startPointIndex);
1277 // The corner iterator pts are tracking "behind" the oval/radii pts.
1278 RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
reed220f9262014-12-17 08:21:04 -08001279 const SkScalar weight = SK_ScalarRoot2Over2;
1280
fmalitac08d53e2015-11-17 09:53:29 -08001281 this->moveTo(ovalIter.current());
1282 for (unsigned i = 0; i < 4; ++i) {
1283 this->conicTo(rectIter.next(), ovalIter.next(), weight);
reed220f9262014-12-17 08:21:04 -08001284 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001285 this->close();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001286
fmalitac08d53e2015-11-17 09:53:29 -08001287 SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
1288
robertphillips@google.com466310d2013-12-03 16:43:54 +00001289 SkPathRef::Editor ed(&fPathRef);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001290
bsalomon78d58d12016-05-27 09:17:04 -07001291 ed.setIsOval(isOval, kCCW_Direction == dir, startPointIndex % 4);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001292}
1293
reed@android.com8a1c16f2008-12-17 15:59:43 +00001294void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1295 if (r > 0) {
fmalitac08d53e2015-11-17 09:53:29 -08001296 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001297 }
1298}
1299
reed@android.com8a1c16f2008-12-17 15:59:43 +00001300void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1301 bool forceMoveTo) {
1302 if (oval.width() < 0 || oval.height() < 0) {
1303 return;
1304 }
1305
reedf90ea012015-01-29 12:03:58 -08001306 if (fPathRef->countVerbs() == 0) {
1307 forceMoveTo = true;
1308 }
1309
1310 SkPoint lonePt;
1311 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
1312 forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
1313 return;
1314 }
1315
reedd5d27d92015-02-09 13:54:43 -08001316 SkVector startV, stopV;
1317 SkRotationDirection dir;
1318 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
1319
reed9e779d42015-02-17 11:43:14 -08001320 SkPoint singlePt;
xidachen6069dda2016-10-06 05:42:23 -07001321
1322 // At this point, we know that the arc is not a lone point, but startV == stopV
1323 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
1324 // cannot handle it.
1325 if (startV == stopV) {
1326 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
1327 SkScalar radiusX = oval.width() / 2;
1328 SkScalar radiusY = oval.height() / 2;
1329 // We cannot use SkScalarSinCos function in the next line because
1330 // SkScalarSinCos has a threshold *SkScalarNearlyZero*. When sin(startAngle)
1331 // is 0 and sweepAngle is very small and radius is huge, the expected
Mike Kleine54c75f2016-10-13 14:18:09 -04001332 // behavior here is to draw a line. But calling SkScalarSinCos will
xidachen6069dda2016-10-06 05:42:23 -07001333 // make sin(endAngle) to be 0 which will then draw a dot.
1334 singlePt.set(oval.centerX() + radiusX * sk_float_cos(endAngle),
1335 oval.centerY() + radiusY * sk_float_sin(endAngle));
1336 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
1337 return;
1338 }
1339
reedd5d27d92015-02-09 13:54:43 -08001340 SkConic conics[SkConic::kMaxConicsForArc];
reed9e779d42015-02-17 11:43:14 -08001341 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
reedd5d27d92015-02-09 13:54:43 -08001342 if (count) {
1343 this->incReserve(count * 2 + 1);
1344 const SkPoint& pt = conics[0].fPts[0];
1345 forceMoveTo ? this->moveTo(pt) : this->lineTo(pt);
1346 for (int i = 0; i < count; ++i) {
1347 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
1348 }
reed9e779d42015-02-17 11:43:14 -08001349 } else {
1350 forceMoveTo ? this->moveTo(singlePt) : this->lineTo(singlePt);
reedd5d27d92015-02-09 13:54:43 -08001351 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352}
1353
caryclark55d49052016-01-23 05:07:04 -08001354// This converts the SVG arc to conics.
1355// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
1356// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
1357// See also SVG implementation notes:
1358// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
1359// Note that arcSweep bool value is flipped from the original implementation.
1360void SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge,
1361 SkPath::Direction arcSweep, SkScalar x, SkScalar y) {
caryclarkf1d41512016-02-09 10:30:22 -08001362 this->injectMoveToIfNeeded();
caryclark55d49052016-01-23 05:07:04 -08001363 SkPoint srcPts[2];
1364 this->getLastPt(&srcPts[0]);
1365 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
1366 // joining the endpoints.
1367 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
1368 if (!rx || !ry) {
caryclarkfc752532016-01-25 05:39:04 -08001369 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001370 return;
1371 }
1372 // If the current point and target point for the arc are identical, it should be treated as a
1373 // zero length path. This ensures continuity in animations.
1374 srcPts[1].set(x, y);
1375 if (srcPts[0] == srcPts[1]) {
caryclarkfc752532016-01-25 05:39:04 -08001376 this->lineTo(x, y);
caryclark55d49052016-01-23 05:07:04 -08001377 return;
1378 }
1379 rx = SkScalarAbs(rx);
1380 ry = SkScalarAbs(ry);
1381 SkVector midPointDistance = srcPts[0] - srcPts[1];
1382 midPointDistance *= 0.5f;
1383
1384 SkMatrix pointTransform;
1385 pointTransform.setRotate(-angle);
1386
1387 SkPoint transformedMidPoint;
1388 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
1389 SkScalar squareRx = rx * rx;
1390 SkScalar squareRy = ry * ry;
1391 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
1392 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
1393
1394 // Check if the radii are big enough to draw the arc, scale radii if not.
1395 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
1396 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
1397 if (radiiScale > 1) {
1398 radiiScale = SkScalarSqrt(radiiScale);
1399 rx *= radiiScale;
1400 ry *= radiiScale;
1401 }
1402
1403 pointTransform.setScale(1 / rx, 1 / ry);
1404 pointTransform.preRotate(-angle);
1405
1406 SkPoint unitPts[2];
1407 pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
1408 SkVector delta = unitPts[1] - unitPts[0];
1409
1410 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
1411 SkScalar scaleFactorSquared = SkTMax(1 / d - 0.25f, 0.f);
1412
1413 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
1414 if (SkToBool(arcSweep) != SkToBool(arcLarge)) { // flipped from the original implementation
1415 scaleFactor = -scaleFactor;
1416 }
1417 delta.scale(scaleFactor);
1418 SkPoint centerPoint = unitPts[0] + unitPts[1];
1419 centerPoint *= 0.5f;
1420 centerPoint.offset(-delta.fY, delta.fX);
1421 unitPts[0] -= centerPoint;
1422 unitPts[1] -= centerPoint;
1423 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
1424 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
1425 SkScalar thetaArc = theta2 - theta1;
1426 if (thetaArc < 0 && !arcSweep) { // arcSweep flipped from the original implementation
1427 thetaArc += SK_ScalarPI * 2;
1428 } else if (thetaArc > 0 && arcSweep) { // arcSweep flipped from the original implementation
1429 thetaArc -= SK_ScalarPI * 2;
1430 }
1431 pointTransform.setRotate(angle);
1432 pointTransform.preScale(rx, ry);
1433
Cary Clark1acd3c72017-12-08 11:37:01 -05001434#ifdef SK_SUPPORT_LEGACY_SVG_ARC_TO
Cary Clark51041372017-12-06 20:07:51 +00001435 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (SK_ScalarPI / 2)));
Cary Clark1acd3c72017-12-08 11:37:01 -05001436#else
1437 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
1438 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
1439#endif
caryclark55d49052016-01-23 05:07:04 -08001440 SkScalar thetaWidth = thetaArc / segments;
1441 SkScalar t = SkScalarTan(0.5f * thetaWidth);
1442 if (!SkScalarIsFinite(t)) {
1443 return;
1444 }
1445 SkScalar startTheta = theta1;
1446 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
Cary Clark1acd3c72017-12-08 11:37:01 -05001447#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1448 auto scalar_is_integer = [](SkScalar scalar) -> bool {
1449 return scalar == SkScalarFloorToScalar(scalar);
1450 };
1451 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
1452 scalar_is_integer(rx) && scalar_is_integer(ry) &&
1453 scalar_is_integer(x) && scalar_is_integer(y);
1454#endif
caryclark55d49052016-01-23 05:07:04 -08001455 for (int i = 0; i < segments; ++i) {
1456 SkScalar endTheta = startTheta + thetaWidth;
1457 SkScalar cosEndTheta, sinEndTheta = SkScalarSinCos(endTheta, &cosEndTheta);
1458
1459 unitPts[1].set(cosEndTheta, sinEndTheta);
1460 unitPts[1] += centerPoint;
1461 unitPts[0] = unitPts[1];
1462 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
1463 SkPoint mapped[2];
1464 pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
Cary Clark1acd3c72017-12-08 11:37:01 -05001465 /*
1466 Computing the arc width introduces rounding errors that cause arcs to start
1467 outside their marks. A round rect may lose convexity as a result. If the input
1468 values are on integers, place the conic on integers as well.
1469 */
1470#ifndef SK_SUPPORT_LEGACY_SVG_ARC_TO
1471 if (expectIntegers) {
1472 SkScalar* mappedScalars = &mapped[0].fX;
1473 for (unsigned index = 0; index < sizeof(mapped) / sizeof(SkScalar); ++index) {
1474 mappedScalars[index] = SkScalarRoundToScalar(mappedScalars[index]);
1475 }
1476 }
1477#endif
caryclark55d49052016-01-23 05:07:04 -08001478 this->conicTo(mapped[0], mapped[1], w);
1479 startTheta = endTheta;
1480 }
1481}
1482
1483void SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc,
1484 SkPath::Direction sweep, SkScalar dx, SkScalar dy) {
1485 SkPoint currentPoint;
1486 this->getLastPt(&currentPoint);
1487 this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy);
1488}
1489
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001490void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 if (oval.isEmpty() || 0 == sweepAngle) {
1492 return;
1493 }
1494
1495 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1496
1497 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
bsalomon1978ce22016-05-31 10:42:16 -07001498 // We can treat the arc as an oval if it begins at one of our legal starting positions.
1499 // See SkPath::addOval() docs.
1500 SkScalar startOver90 = startAngle / 90.f;
1501 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
1502 SkScalar error = startOver90 - startOver90I;
1503 if (SkScalarNearlyEqual(error, 0)) {
1504 // Index 1 is at startAngle == 0.
1505 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
1506 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
1507 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction,
1508 (unsigned) startIndex);
1509 return;
1510 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511 }
bsalomon1978ce22016-05-31 10:42:16 -07001512 this->arcTo(oval, startAngle, sweepAngle, true);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513}
1514
1515/*
1516 Need to handle the case when the angle is sharp, and our computed end-points
1517 for the arc go behind pt1 and/or p2...
1518*/
reedc7789042015-01-29 12:59:11 -08001519void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
reeda8b326c2014-12-09 11:50:32 -08001520 if (radius == 0) {
1521 this->lineTo(x1, y1);
1522 return;
1523 }
1524
1525 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001526
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 // need to know our prev pt so we can construct tangent vectors
1528 {
1529 SkPoint start;
1530 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001531 // Handle degenerate cases by adding a line to the first point and
1532 // bailing out.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 before.setNormalize(x1 - start.fX, y1 - start.fY);
1534 after.setNormalize(x2 - x1, y2 - y1);
1535 }
reed@google.comabf15c12011-01-18 20:35:51 +00001536
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 SkScalar cosh = SkPoint::DotProduct(before, after);
1538 SkScalar sinh = SkPoint::CrossProduct(before, after);
1539
1540 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001541 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 return;
1543 }
reed@google.comabf15c12011-01-18 20:35:51 +00001544
Mike Reeda99b6ce2017-02-04 11:04:26 -05001545 SkScalar dist = SkScalarAbs(radius * (1 - cosh) / sinh);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546
Mike Reeda99b6ce2017-02-04 11:04:26 -05001547 SkScalar xx = x1 - dist * before.fX;
1548 SkScalar yy = y1 - dist * before.fY;
caryclark88651ae2016-01-20 11:55:11 -08001549 after.setLength(dist);
1550 this->lineTo(xx, yy);
1551 SkScalar weight = SkScalarSqrt(SK_ScalarHalf + cosh * SK_ScalarHalf);
1552 this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001553}
1554
1555///////////////////////////////////////////////////////////////////////////////
1556
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001557void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001558 SkMatrix matrix;
1559
1560 matrix.setTranslate(dx, dy);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001561 this->addPath(path, matrix, mode);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001562}
1563
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001564void SkPath::addPath(const SkPath& path, const SkMatrix& matrix, AddPathMode mode) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001565 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001567 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001568 SkPoint pts[4];
1569 Verb verb;
1570
Cary Clark9480d822017-10-19 18:01:13 -04001571 SkMatrixPriv::MapPtsProc proc = SkMatrixPriv::GetMapPtsProc(matrix);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001572 bool firstVerb = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 while ((verb = iter.next(pts)) != kDone_Verb) {
1574 switch (verb) {
1575 case kMove_Verb:
1576 proc(matrix, &pts[0], &pts[0], 1);
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001577 if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) {
1578 injectMoveToIfNeeded(); // In case last contour is closed
1579 this->lineTo(pts[0]);
1580 } else {
1581 this->moveTo(pts[0]);
1582 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001583 break;
1584 case kLine_Verb:
1585 proc(matrix, &pts[1], &pts[1], 1);
1586 this->lineTo(pts[1]);
1587 break;
1588 case kQuad_Verb:
1589 proc(matrix, &pts[1], &pts[1], 2);
1590 this->quadTo(pts[1], pts[2]);
1591 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001592 case kConic_Verb:
1593 proc(matrix, &pts[1], &pts[1], 2);
1594 this->conicTo(pts[1], pts[2], iter.conicWeight());
1595 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001596 case kCubic_Verb:
1597 proc(matrix, &pts[1], &pts[1], 3);
1598 this->cubicTo(pts[1], pts[2], pts[3]);
1599 break;
1600 case kClose_Verb:
1601 this->close();
1602 break;
1603 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001604 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 }
commit-bot@chromium.org14747e52014-02-11 21:16:29 +00001606 firstVerb = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001607 }
1608}
1609
1610///////////////////////////////////////////////////////////////////////////////
1611
reed@google.com277c3f82013-05-31 15:17:50 +00001612static int pts_in_verb(unsigned verb) {
1613 static const uint8_t gPtsInVerb[] = {
1614 1, // kMove
1615 1, // kLine
1616 2, // kQuad
1617 2, // kConic
1618 3, // kCubic
1619 0, // kClose
1620 0 // kDone
1621 };
1622
1623 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1624 return gPtsInVerb[verb];
1625}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001626
reed@android.com8a1c16f2008-12-17 15:59:43 +00001627// ignore the last point of the 1st contour
1628void SkPath::reversePathTo(const SkPath& path) {
caryclark51c56782016-11-07 05:09:28 -08001629 const uint8_t* verbs = path.fPathRef->verbsMemBegin(); // points at the last verb
1630 if (!verbs) { // empty path returns nullptr
reed@android.com8a1c16f2008-12-17 15:59:43 +00001631 return;
1632 }
caryclark51c56782016-11-07 05:09:28 -08001633 const uint8_t* verbsEnd = path.fPathRef->verbs() - 1; // points just past the first verb
1634 SkASSERT(verbsEnd[0] == kMove_Verb);
1635 const SkPoint* pts = path.fPathRef->pointsEnd() - 1;
1636 const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637
caryclark51c56782016-11-07 05:09:28 -08001638 while (verbs < verbsEnd) {
1639 uint8_t v = *verbs++;
1640 pts -= pts_in_verb(v);
1641 switch (v) {
1642 case kMove_Verb:
1643 // if the path has multiple contours, stop after reversing the last
1644 return;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 case kLine_Verb:
caryclark51c56782016-11-07 05:09:28 -08001646 this->lineTo(pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001647 break;
1648 case kQuad_Verb:
caryclark51c56782016-11-07 05:09:28 -08001649 this->quadTo(pts[1], pts[0]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001651 case kConic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001652 this->conicTo(pts[1], pts[0], *--conicWeights);
reed@google.com277c3f82013-05-31 15:17:50 +00001653 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 case kCubic_Verb:
caryclark51c56782016-11-07 05:09:28 -08001655 this->cubicTo(pts[2], pts[1], pts[0]);
1656 break;
1657 case kClose_Verb:
1658 SkASSERT(verbs - path.fPathRef->verbsMemBegin() == 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001659 break;
1660 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001661 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001662 break;
1663 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 }
1665}
1666
reed@google.com63d73742012-01-10 15:33:12 +00001667void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001669
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001670 const SkPoint* pts = src.fPathRef->pointsEnd();
1671 // we will iterator through src's verbs backwards
1672 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1673 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001674 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001675
1676 bool needMove = true;
1677 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001678 while (verbs < verbsEnd) {
1679 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001680 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001681
1682 if (needMove) {
1683 --pts;
1684 this->moveTo(pts->fX, pts->fY);
1685 needMove = false;
1686 }
1687 pts -= n;
1688 switch (v) {
1689 case kMove_Verb:
1690 if (needClose) {
1691 this->close();
1692 needClose = false;
1693 }
1694 needMove = true;
1695 pts += 1; // so we see the point in "if (needMove)" above
1696 break;
1697 case kLine_Verb:
1698 this->lineTo(pts[0]);
1699 break;
1700 case kQuad_Verb:
1701 this->quadTo(pts[1], pts[0]);
1702 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001703 case kConic_Verb:
1704 this->conicTo(pts[1], pts[0], *--conicWeights);
1705 break;
reed@google.com63d73742012-01-10 15:33:12 +00001706 case kCubic_Verb:
1707 this->cubicTo(pts[2], pts[1], pts[0]);
1708 break;
1709 case kClose_Verb:
1710 needClose = true;
1711 break;
1712 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001713 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001714 }
1715 }
1716}
1717
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718///////////////////////////////////////////////////////////////////////////////
1719
1720void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1721 SkMatrix matrix;
1722
1723 matrix.setTranslate(dx, dy);
1724 this->transform(matrix, dst);
1725}
1726
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1728 int level = 2) {
1729 if (--level >= 0) {
1730 SkPoint tmp[7];
1731
1732 SkChopCubicAtHalf(pts, tmp);
1733 subdivide_cubic_to(path, &tmp[0], level);
1734 subdivide_cubic_to(path, &tmp[3], level);
1735 } else {
1736 path->cubicTo(pts[1], pts[2], pts[3]);
1737 }
1738}
1739
1740void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1741 SkDEBUGCODE(this->validate();)
halcanary96fcdcc2015-08-27 07:41:13 -07001742 if (dst == nullptr) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 dst = (SkPath*)this;
1744 }
1745
tomhudson@google.com8d430182011-06-06 19:11:19 +00001746 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747 SkPath tmp;
1748 tmp.fFillType = fFillType;
1749
1750 SkPath::Iter iter(*this, false);
1751 SkPoint pts[4];
1752 SkPath::Verb verb;
1753
reed@google.com4a3b7142012-05-16 17:16:46 +00001754 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 switch (verb) {
1756 case kMove_Verb:
1757 tmp.moveTo(pts[0]);
1758 break;
1759 case kLine_Verb:
1760 tmp.lineTo(pts[1]);
1761 break;
1762 case kQuad_Verb:
reed220f9262014-12-17 08:21:04 -08001763 // promote the quad to a conic
1764 tmp.conicTo(pts[1], pts[2],
1765 SkConic::TransformW(pts, SK_Scalar1, matrix));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001766 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001767 case kConic_Verb:
reed220f9262014-12-17 08:21:04 -08001768 tmp.conicTo(pts[1], pts[2],
1769 SkConic::TransformW(pts, iter.conicWeight(), matrix));
reed@google.com277c3f82013-05-31 15:17:50 +00001770 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 case kCubic_Verb:
1772 subdivide_cubic_to(&tmp, pts);
1773 break;
1774 case kClose_Verb:
1775 tmp.close();
1776 break;
1777 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001778 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 break;
1780 }
1781 }
1782
1783 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001784 SkPathRef::Editor ed(&dst->fPathRef);
1785 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
reed026beb52015-06-10 14:23:15 -07001786 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001788 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1789
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 dst->fFillType = fFillType;
Mike Kleinb9b5de52017-09-27 13:26:22 -04001792 dst->fConvexity.store(fConvexity);
jvanverthb3eb6872014-10-24 07:12:51 -07001793 dst->fIsVolatile = fIsVolatile;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001794 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001795
reed026beb52015-06-10 14:23:15 -07001796 if (SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
1797 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001798 } else {
1799 SkScalar det2x2 =
Mike Reeda99b6ce2017-02-04 11:04:26 -05001800 matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) -
1801 matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001802 if (det2x2 < 0) {
herb9f4dbca2015-09-28 11:05:47 -07001803 dst->fFirstDirection = SkPathPriv::OppositeFirstDirection(
1804 (SkPathPriv::FirstDirection)fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001805 } else if (det2x2 > 0) {
herb9f4dbca2015-09-28 11:05:47 -07001806 dst->fFirstDirection = fFirstDirection.load();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001807 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00001808 dst->fConvexity = kUnknown_Convexity;
reed026beb52015-06-10 14:23:15 -07001809 dst->fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001810 }
1811 }
1812
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 SkDEBUGCODE(dst->validate();)
1814 }
1815}
1816
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817///////////////////////////////////////////////////////////////////////////////
1818///////////////////////////////////////////////////////////////////////////////
1819
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001821 kEmptyContour_SegmentState, // The current contour is empty. We may be
1822 // starting processing or we may have just
1823 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001824 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1825 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1826 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827};
1828
1829SkPath::Iter::Iter() {
1830#ifdef SK_DEBUG
halcanary96fcdcc2015-08-27 07:41:13 -07001831 fPts = nullptr;
1832 fConicWeights = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001833 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001834 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001835 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001836#endif
1837 // need to init enough to make next() harmlessly return kDone_Verb
halcanary96fcdcc2015-08-27 07:41:13 -07001838 fVerbs = nullptr;
1839 fVerbStop = nullptr;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001840 fNeedClose = false;
1841}
1842
1843SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1844 this->setPath(path, forceClose);
1845}
1846
1847void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001848 fPts = path.fPathRef->points();
1849 fVerbs = path.fPathRef->verbs();
1850 fVerbStop = path.fPathRef->verbsMemBegin();
caryclark69424422016-10-04 13:06:17 -07001851 fConicWeights = path.fPathRef->conicWeights();
1852 if (fConicWeights) {
1853 fConicWeights -= 1; // begin one behind
1854 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001855 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001856 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857 fForceClose = SkToU8(forceClose);
1858 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001859 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001860}
1861
1862bool SkPath::Iter::isClosedContour() const {
halcanary96fcdcc2015-08-27 07:41:13 -07001863 if (fVerbs == nullptr || fVerbs == fVerbStop) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864 return false;
1865 }
1866 if (fForceClose) {
1867 return true;
1868 }
1869
1870 const uint8_t* verbs = fVerbs;
1871 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001872
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001873 if (kMove_Verb == *(verbs - 1)) {
1874 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001875 }
1876
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001877 while (verbs > stop) {
1878 // verbs points one beyond the current verb, decrement first.
1879 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001880 if (kMove_Verb == v) {
1881 break;
1882 }
1883 if (kClose_Verb == v) {
1884 return true;
1885 }
1886 }
1887 return false;
1888}
1889
1890SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001891 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001892 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001893 // A special case: if both points are NaN, SkPoint::operation== returns
1894 // false, but the iterator expects that they are treated as the same.
1895 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001896 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1897 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001898 return kClose_Verb;
1899 }
1900
reed@google.com9e25dbf2012-05-15 17:05:38 +00001901 pts[0] = fLastPt;
1902 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 fLastPt = fMoveTo;
1904 fCloseLine = true;
1905 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001906 } else {
1907 pts[0] = fMoveTo;
1908 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001909 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910}
1911
reed@google.com9e25dbf2012-05-15 17:05:38 +00001912const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 if (fSegmentState == kAfterMove_SegmentState) {
1914 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001915 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001916 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1919 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001920 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001922}
1923
caryclarke8c56662015-07-14 11:19:26 -07001924void SkPath::Iter::consumeDegenerateSegments(bool exact) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 // We need to step over anything that will not move the current draw point
1926 // forward before the next move is seen
Ben Wagnera93a14a2017-08-28 10:34:05 -04001927 const uint8_t* lastMoveVerb = nullptr;
1928 const SkPoint* lastMovePt = nullptr;
halcanary96fcdcc2015-08-27 07:41:13 -07001929 const SkScalar* lastMoveWeight = nullptr;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001930 SkPoint lastPt = fLastPt;
1931 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 switch (verb) {
1934 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 // Keep a record of this most recent move
1936 lastMoveVerb = fVerbs;
1937 lastMovePt = fPts;
robertphillipsb8de1f42015-02-23 11:17:01 -08001938 lastMoveWeight = fConicWeights;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001939 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001940 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001941 fPts++;
1942 break;
1943
1944 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001945 // A close when we are in a segment is always valid except when it
1946 // follows a move which follows a segment.
1947 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 return;
1949 }
1950 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001951 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 break;
1953
1954 case kLine_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001955 if (!IsLineDegenerate(lastPt, fPts[0], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001956 if (lastMoveVerb) {
1957 fVerbs = lastMoveVerb;
1958 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001959 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001960 return;
1961 }
1962 return;
1963 }
1964 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001965 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001966 fPts++;
1967 break;
1968
reed@google.com277c3f82013-05-31 15:17:50 +00001969 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001970 case kQuad_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001971 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001972 if (lastMoveVerb) {
1973 fVerbs = lastMoveVerb;
1974 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001975 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001976 return;
1977 }
1978 return;
1979 }
1980 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001981 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001982 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001983 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001984 break;
1985
1986 case kCubic_Verb:
caryclarke8c56662015-07-14 11:19:26 -07001987 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2], exact)) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001988 if (lastMoveVerb) {
1989 fVerbs = lastMoveVerb;
1990 fPts = lastMovePt;
robertphillipsb8de1f42015-02-23 11:17:01 -08001991 fConicWeights = lastMoveWeight;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 return;
1993 }
1994 return;
1995 }
1996 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001997 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001998 fPts += 3;
1999 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002000
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002001 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002002 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002003 }
2004 }
2005}
2006
reed@google.com4a3b7142012-05-16 17:16:46 +00002007SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002008 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002009
reed@android.com8a1c16f2008-12-17 15:59:43 +00002010 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002011 // Close the curve if requested and if there is some curve to close
2012 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00002013 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014 return kLine_Verb;
2015 }
2016 fNeedClose = false;
2017 return kClose_Verb;
2018 }
2019 return kDone_Verb;
2020 }
2021
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002022 // fVerbs is one beyond the current verb, decrement first
2023 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00002024 const SkPoint* SK_RESTRICT srcPts = fPts;
2025 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002026
2027 switch (verb) {
2028 case kMove_Verb:
2029 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002030 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002031 verb = this->autoClose(pts);
2032 if (verb == kClose_Verb) {
2033 fNeedClose = false;
2034 }
2035 return (Verb)verb;
2036 }
2037 if (fVerbs == fVerbStop) { // might be a trailing moveto
2038 return kDone_Verb;
2039 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002040 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00002041 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00002043 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002044 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045 fNeedClose = fForceClose;
2046 break;
2047 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002048 pts[0] = this->cons_moveTo();
2049 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 fLastPt = srcPts[0];
2051 fCloseLine = false;
2052 srcPts += 1;
2053 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002054 case kConic_Verb:
2055 fConicWeights += 1;
2056 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00002057 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002058 pts[0] = this->cons_moveTo();
2059 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060 fLastPt = srcPts[1];
2061 srcPts += 2;
2062 break;
2063 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002064 pts[0] = this->cons_moveTo();
2065 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002066 fLastPt = srcPts[2];
2067 srcPts += 3;
2068 break;
2069 case kClose_Verb:
2070 verb = this->autoClose(pts);
2071 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002072 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073 } else {
2074 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002075 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002077 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002078 break;
2079 }
2080 fPts = srcPts;
2081 return (Verb)verb;
2082}
2083
2084///////////////////////////////////////////////////////////////////////////////
2085
Ben Wagner4d1955c2017-03-10 13:08:15 -05002086#include "SkString.h"
reede05fed02014-12-15 07:59:53 -08002087#include "SkStringUtils.h"
caryclark66a5d8b2014-06-24 08:30:15 -07002088#include "SkStream.h"
reed@google.com51bbe752013-01-17 22:07:50 +00002089
reed@google.com51bbe752013-01-17 22:07:50 +00002090static void append_params(SkString* str, const char label[], const SkPoint pts[],
Mike Reed405b9d22018-01-09 15:11:08 -05002091 int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) {
reed@google.com51bbe752013-01-17 22:07:50 +00002092 str->append(label);
2093 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002094
reed@google.com51bbe752013-01-17 22:07:50 +00002095 const SkScalar* values = &pts[0].fX;
2096 count *= 2;
2097
2098 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002099 SkAppendScalar(str, values[i], strType);
reed@google.com51bbe752013-01-17 22:07:50 +00002100 if (i < count - 1) {
2101 str->append(", ");
2102 }
2103 }
Mike Reed405b9d22018-01-09 15:11:08 -05002104 if (conicWeight != -12345) {
reed@google.com277c3f82013-05-31 15:17:50 +00002105 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002106 SkAppendScalar(str, conicWeight, strType);
reed@google.com277c3f82013-05-31 15:17:50 +00002107 }
caryclark08fa28c2014-10-23 13:08:56 -07002108 str->append(");");
reede05fed02014-12-15 07:59:53 -08002109 if (kHex_SkScalarAsStringType == strType) {
caryclark08fa28c2014-10-23 13:08:56 -07002110 str->append(" // ");
2111 for (int i = 0; i < count; ++i) {
reede05fed02014-12-15 07:59:53 -08002112 SkAppendScalarDec(str, values[i]);
caryclark08fa28c2014-10-23 13:08:56 -07002113 if (i < count - 1) {
2114 str->append(", ");
2115 }
2116 }
2117 if (conicWeight >= 0) {
2118 str->append(", ");
reede05fed02014-12-15 07:59:53 -08002119 SkAppendScalarDec(str, conicWeight);
caryclark08fa28c2014-10-23 13:08:56 -07002120 }
2121 }
2122 str->append("\n");
reed@google.com51bbe752013-01-17 22:07:50 +00002123}
2124
caryclarke9562592014-09-15 09:26:09 -07002125void SkPath::dump(SkWStream* wStream, bool forceClose, bool dumpAsHex) const {
reede05fed02014-12-15 07:59:53 -08002126 SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002127 Iter iter(*this, forceClose);
2128 SkPoint pts[4];
2129 Verb verb;
2130
reed@google.com51bbe752013-01-17 22:07:50 +00002131 SkString builder;
Cary Clark9f67f042016-12-16 11:32:54 -05002132 char const * const gFillTypeStrs[] = {
2133 "Winding",
2134 "EvenOdd",
2135 "InverseWinding",
2136 "InverseEvenOdd",
2137 };
2138 builder.printf("path.setFillType(SkPath::k%s_FillType);\n",
2139 gFillTypeStrs[(int) this->getFillType()]);
reed@google.com4a3b7142012-05-16 17:16:46 +00002140 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 switch (verb) {
2142 case kMove_Verb:
reede05fed02014-12-15 07:59:53 -08002143 append_params(&builder, "path.moveTo", &pts[0], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
2145 case kLine_Verb:
reede05fed02014-12-15 07:59:53 -08002146 append_params(&builder, "path.lineTo", &pts[1], 1, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002147 break;
2148 case kQuad_Verb:
reede05fed02014-12-15 07:59:53 -08002149 append_params(&builder, "path.quadTo", &pts[1], 2, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002151 case kConic_Verb:
reede05fed02014-12-15 07:59:53 -08002152 append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight());
reed@google.com277c3f82013-05-31 15:17:50 +00002153 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002154 case kCubic_Verb:
reede05fed02014-12-15 07:59:53 -08002155 append_params(&builder, "path.cubicTo", &pts[1], 3, asType);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002156 break;
2157 case kClose_Verb:
caryclark66a5d8b2014-06-24 08:30:15 -07002158 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159 break;
2160 default:
2161 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2162 verb = kDone_Verb; // stop the loop
2163 break;
2164 }
caryclark1049f122015-04-20 08:31:59 -07002165 if (!wStream && builder.size()) {
2166 SkDebugf("%s", builder.c_str());
2167 builder.reset();
2168 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002169 }
caryclark66a5d8b2014-06-24 08:30:15 -07002170 if (wStream) {
2171 wStream->writeText(builder.c_str());
caryclark66a5d8b2014-06-24 08:30:15 -07002172 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173}
2174
reed@android.come522ca52009-11-23 20:10:41 +00002175void SkPath::dump() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002176 this->dump(nullptr, false, false);
caryclarke9562592014-09-15 09:26:09 -07002177}
2178
2179void SkPath::dumpHex() const {
halcanary96fcdcc2015-08-27 07:41:13 -07002180 this->dump(nullptr, false, true);
reed@android.come522ca52009-11-23 20:10:41 +00002181}
2182
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002183
Cary Clark0461e9f2017-08-25 15:13:38 -04002184bool SkPath::isValidImpl() const {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002185 if ((fFillType & ~3) != 0) {
2186 return false;
2187 }
reed@google.comabf15c12011-01-18 20:35:51 +00002188
djsollen@google.com077348c2012-10-22 20:23:32 +00002189#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002190 if (!fBoundsIsDirty) {
2191 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002192
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002193 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002194 if (SkToBool(fIsFinite) != isFinite) {
2195 return false;
2196 }
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002197
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002198 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002199 // if we're empty, fBounds may be empty but translated, so we can't
2200 // necessarily compare to bounds directly
2201 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2202 // be [2, 2, 2, 2]
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002203 if (!bounds.isEmpty() || !fBounds.isEmpty()) {
2204 return false;
2205 }
reed@android.come522ca52009-11-23 20:10:41 +00002206 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002207 if (bounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002208 if (!fBounds.isEmpty()) {
2209 return false;
2210 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002211 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002212 if (!fBounds.isEmpty()) {
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002213 if (!fBounds.contains(bounds)) {
2214 return false;
2215 }
reed@google.com3563c9e2011-11-14 19:34:57 +00002216 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002217 }
reed@android.come522ca52009-11-23 20:10:41 +00002218 }
2219 }
djsollen@google.com077348c2012-10-22 20:23:32 +00002220#endif // SK_DEBUG_PATH
Adrienne Walkerad8da8e2017-08-10 12:16:37 -07002221 return true;
reed@android.come522ca52009-11-23 20:10:41 +00002222}
reed@android.come522ca52009-11-23 20:10:41 +00002223
reed@google.com04863fa2011-05-15 04:08:24 +00002224///////////////////////////////////////////////////////////////////////////////
2225
reed@google.com0b7b9822011-05-16 12:29:27 +00002226static int sign(SkScalar x) { return x < 0; }
2227#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002228
robertphillipsc506e302014-09-16 09:43:31 -07002229enum DirChange {
2230 kLeft_DirChange,
2231 kRight_DirChange,
2232 kStraight_DirChange,
2233 kBackwards_DirChange,
2234
2235 kInvalid_DirChange
2236};
2237
2238
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002239static bool almost_equal(SkScalar compA, SkScalar compB) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002240 // The error epsilon was empirically derived; worse case round rects
2241 // with a mid point outset by 2x float epsilon in tests had an error
2242 // of 12.
2243 const int epsilon = 16;
2244 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2245 return false;
2246 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002247 // no need to check for small numbers because SkPath::Iter has removed degenerate values
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002248 int aBits = SkFloatAs2sCompliment(compA);
2249 int bBits = SkFloatAs2sCompliment(compB);
2250 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002251}
2252
caryclarkb4216502015-03-02 13:02:34 -08002253static bool approximately_zero_when_compared_to(double x, double y) {
2254 return x == 0 || fabs(x) < fabs(y * FLT_EPSILON);
robertphillipsc506e302014-09-16 09:43:31 -07002255}
2256
caryclarkb4216502015-03-02 13:02:34 -08002257
reed@google.com04863fa2011-05-15 04:08:24 +00002258// only valid for a single contour
2259struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002260 Convexicator()
2261 : fPtCount(0)
2262 , fConvexity(SkPath::kConvex_Convexity)
reed026beb52015-06-10 14:23:15 -07002263 , fFirstDirection(SkPathPriv::kUnknown_FirstDirection)
caryclark5ccef572015-03-02 10:07:56 -08002264 , fIsFinite(true)
Cary Clarkc8323aa2017-08-25 08:04:43 -04002265 , fIsCurve(false)
2266 , fBackwards(false) {
robertphillipsc506e302014-09-16 09:43:31 -07002267 fExpectedDir = kInvalid_DirChange;
reed@google.com04863fa2011-05-15 04:08:24 +00002268 // warnings
djsollen2f124632016-04-29 13:53:05 -07002269 fPriorPt.set(0,0);
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002270 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002271 fCurrPt.set(0, 0);
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002272 fLastVec.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002273 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002274
2275 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002276 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002277 }
2278
2279 SkPath::Convexity getConvexity() const { return fConvexity; }
2280
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002281 /** The direction returned is only valid if the path is determined convex */
reed026beb52015-06-10 14:23:15 -07002282 SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002283
reed@google.com04863fa2011-05-15 04:08:24 +00002284 void addPt(const SkPoint& pt) {
caryclarkd3d1a982014-12-08 04:57:38 -08002285 if (SkPath::kConcave_Convexity == fConvexity || !fIsFinite) {
reed@google.com04863fa2011-05-15 04:08:24 +00002286 return;
2287 }
2288
2289 if (0 == fPtCount) {
2290 fCurrPt = pt;
2291 ++fPtCount;
2292 } else {
2293 SkVector vec = pt - fCurrPt;
Cary Clarkdf429f32017-11-08 11:44:31 -05002294 SkScalar lengthSqd = SkPointPriv::LengthSqd(vec);
caryclarkd3d1a982014-12-08 04:57:38 -08002295 if (!SkScalarIsFinite(lengthSqd)) {
2296 fIsFinite = false;
caryclarke8c56662015-07-14 11:19:26 -07002297 } else if (lengthSqd) {
caryclarkb4216502015-03-02 13:02:34 -08002298 fPriorPt = fLastPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002299 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002300 fCurrPt = pt;
2301 if (++fPtCount == 2) {
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002302 fFirstVec = fLastVec = vec;
reed@google.com04863fa2011-05-15 04:08:24 +00002303 } else {
2304 SkASSERT(fPtCount > 2);
2305 this->addVec(vec);
2306 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002307
reed@google.com85b6e392011-05-15 20:25:17 +00002308 int sx = sign(vec.fX);
2309 int sy = sign(vec.fY);
2310 fDx += (sx != fSx);
2311 fDy += (sy != fSy);
2312 fSx = sx;
2313 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002314
reed@google.com85b6e392011-05-15 20:25:17 +00002315 if (fDx > 3 || fDy > 3) {
2316 fConvexity = SkPath::kConcave_Convexity;
2317 }
reed@google.com04863fa2011-05-15 04:08:24 +00002318 }
2319 }
2320 }
2321
2322 void close() {
2323 if (fPtCount > 2) {
2324 this->addVec(fFirstVec);
2325 }
2326 }
2327
caryclarkb4216502015-03-02 13:02:34 -08002328 DirChange directionChange(const SkVector& curVec) {
2329 SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec);
2330
2331 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2332 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2333 largest = SkTMax(largest, -smallest);
2334
2335 if (!almost_equal(largest, largest + cross)) {
2336 int sign = SkScalarSignAsInt(cross);
2337 if (sign) {
2338 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2339 }
2340 }
2341
2342 if (cross) {
2343 double dLastVecX = SkScalarToDouble(fLastPt.fX) - SkScalarToDouble(fPriorPt.fX);
2344 double dLastVecY = SkScalarToDouble(fLastPt.fY) - SkScalarToDouble(fPriorPt.fY);
2345 double dCurrVecX = SkScalarToDouble(fCurrPt.fX) - SkScalarToDouble(fLastPt.fX);
2346 double dCurrVecY = SkScalarToDouble(fCurrPt.fY) - SkScalarToDouble(fLastPt.fY);
2347 double dCross = dLastVecX * dCurrVecY - dLastVecY * dCurrVecX;
2348 if (!approximately_zero_when_compared_to(dCross, SkScalarToDouble(largest))) {
2349 int sign = SkScalarSignAsInt(SkDoubleToScalar(dCross));
2350 if (sign) {
2351 return (1 == sign) ? kRight_DirChange : kLeft_DirChange;
2352 }
2353 }
2354 }
2355
Cary Clarkdf429f32017-11-08 11:44:31 -05002356 if (!SkScalarNearlyZero(SkPointPriv::LengthSqd(fLastVec),
2357 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
2358 !SkScalarNearlyZero(SkPointPriv::LengthSqd(curVec),
2359 SK_ScalarNearlyZero*SK_ScalarNearlyZero) &&
caryclarkb4216502015-03-02 13:02:34 -08002360 fLastVec.dot(curVec) < 0.0f) {
2361 return kBackwards_DirChange;
2362 }
2363
2364 return kStraight_DirChange;
2365 }
2366
Cary Clarkc8323aa2017-08-25 08:04:43 -04002367 bool hasBackwards() const {
2368 return fBackwards;
2369 }
caryclarkb4216502015-03-02 13:02:34 -08002370
caryclarkd3d1a982014-12-08 04:57:38 -08002371 bool isFinite() const {
2372 return fIsFinite;
2373 }
2374
caryclark5ccef572015-03-02 10:07:56 -08002375 void setCurve(bool isCurve) {
2376 fIsCurve = isCurve;
2377 }
2378
reed@google.com04863fa2011-05-15 04:08:24 +00002379private:
2380 void addVec(const SkVector& vec) {
2381 SkASSERT(vec.fX || vec.fY);
caryclarkb4216502015-03-02 13:02:34 -08002382 DirChange dir = this->directionChange(vec);
robertphillipsc506e302014-09-16 09:43:31 -07002383 switch (dir) {
2384 case kLeft_DirChange: // fall through
2385 case kRight_DirChange:
2386 if (kInvalid_DirChange == fExpectedDir) {
2387 fExpectedDir = dir;
reed026beb52015-06-10 14:23:15 -07002388 fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
2389 : SkPathPriv::kCCW_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002390 } else if (dir != fExpectedDir) {
2391 fConvexity = SkPath::kConcave_Convexity;
reed026beb52015-06-10 14:23:15 -07002392 fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
robertphillipsc506e302014-09-16 09:43:31 -07002393 }
2394 fLastVec = vec;
2395 break;
2396 case kStraight_DirChange:
2397 break;
2398 case kBackwards_DirChange:
caryclark5ccef572015-03-02 10:07:56 -08002399 if (fIsCurve) {
liyuqianbdabcc42016-11-03 11:40:19 -07002400 // If any of the subsequent dir is non-backward, it'll be concave.
2401 // Otherwise, it's still convex.
2402 fExpectedDir = dir;
caryclark5ccef572015-03-02 10:07:56 -08002403 }
robertphillipsc506e302014-09-16 09:43:31 -07002404 fLastVec = vec;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002405 fBackwards = true;
robertphillipsc506e302014-09-16 09:43:31 -07002406 break;
2407 case kInvalid_DirChange:
Ben Wagnerb4aab9a2017-08-16 10:53:04 -04002408 SK_ABORT("Use of invalid direction change flag");
robertphillipsc506e302014-09-16 09:43:31 -07002409 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002410 }
2411 }
2412
caryclarkb4216502015-03-02 13:02:34 -08002413 SkPoint fPriorPt;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002414 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002415 SkPoint fCurrPt;
commit-bot@chromium.org8be07bb2014-05-22 14:58:53 +00002416 // fLastVec does not necessarily start at fLastPt. We only advance it when the cross product
2417 // value with the current vec is deemed to be of a significant value.
2418 SkVector fLastVec, fFirstVec;
reed@google.com04863fa2011-05-15 04:08:24 +00002419 int fPtCount; // non-degenerate points
robertphillipsc506e302014-09-16 09:43:31 -07002420 DirChange fExpectedDir;
reed@google.com04863fa2011-05-15 04:08:24 +00002421 SkPath::Convexity fConvexity;
reed026beb52015-06-10 14:23:15 -07002422 SkPathPriv::FirstDirection fFirstDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002423 int fDx, fDy, fSx, fSy;
caryclarkd3d1a982014-12-08 04:57:38 -08002424 bool fIsFinite;
caryclark5ccef572015-03-02 10:07:56 -08002425 bool fIsCurve;
Cary Clarkc8323aa2017-08-25 08:04:43 -04002426 bool fBackwards;
reed@google.com04863fa2011-05-15 04:08:24 +00002427};
2428
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002429SkPath::Convexity SkPath::internalGetConvexity() const {
2430 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002431 SkPoint pts[4];
2432 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002433 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002434
2435 int contourCount = 0;
2436 int count;
2437 Convexicator state;
2438
caryclarkd3d1a982014-12-08 04:57:38 -08002439 if (!isFinite()) {
2440 return kUnknown_Convexity;
2441 }
Brian Osman205a1262017-09-18 13:13:48 +00002442 while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
reed@google.com04863fa2011-05-15 04:08:24 +00002443 switch (verb) {
2444 case kMove_Verb:
2445 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002446 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002447 return kConcave_Convexity;
2448 }
2449 pts[1] = pts[0];
caryclark5ccef572015-03-02 10:07:56 -08002450 // fall through
2451 case kLine_Verb:
reed@google.com04863fa2011-05-15 04:08:24 +00002452 count = 1;
caryclark5ccef572015-03-02 10:07:56 -08002453 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002454 break;
caryclark5ccef572015-03-02 10:07:56 -08002455 case kQuad_Verb:
2456 // fall through
2457 case kConic_Verb:
2458 // fall through
2459 case kCubic_Verb:
2460 count = 2 + (kCubic_Verb == verb);
2461 // As an additional enhancement, this could set curve true only
2462 // if the curve is nonlinear
2463 state.setCurve(true);
2464 break;
reed@google.com04863fa2011-05-15 04:08:24 +00002465 case kClose_Verb:
caryclark5ccef572015-03-02 10:07:56 -08002466 state.setCurve(false);
reed@google.com04863fa2011-05-15 04:08:24 +00002467 state.close();
2468 count = 0;
2469 break;
2470 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002471 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002472 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002473 return kConcave_Convexity;
2474 }
2475
2476 for (int i = 1; i <= count; i++) {
2477 state.addPt(pts[i]);
2478 }
2479 // early exit
caryclarkd3d1a982014-12-08 04:57:38 -08002480 if (!state.isFinite()) {
2481 return kUnknown_Convexity;
2482 }
reed@google.com04863fa2011-05-15 04:08:24 +00002483 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002484 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002485 return kConcave_Convexity;
2486 }
2487 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002488 fConvexity = state.getConvexity();
reed026beb52015-06-10 14:23:15 -07002489 if (kConvex_Convexity == fConvexity && SkPathPriv::kUnknown_FirstDirection == fFirstDirection) {
Cary Clarkc8323aa2017-08-25 08:04:43 -04002490 if (SkPathPriv::kUnknown_FirstDirection == state.getFirstDirection() &&
2491 !this->getBounds().isEmpty() && !state.hasBackwards()) {
2492 fConvexity = Convexity::kConcave_Convexity;
2493 } else {
2494 fFirstDirection = state.getFirstDirection();
2495 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002496 }
2497 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002498}
reed@google.com69a99432012-01-10 18:00:10 +00002499
2500///////////////////////////////////////////////////////////////////////////////
2501
2502class ContourIter {
2503public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002504 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002505
2506 bool done() const { return fDone; }
2507 // if !done() then these may be called
2508 int count() const { return fCurrPtCount; }
2509 const SkPoint* pts() const { return fCurrPt; }
2510 void next();
2511
2512private:
2513 int fCurrPtCount;
2514 const SkPoint* fCurrPt;
2515 const uint8_t* fCurrVerb;
2516 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002517 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002518 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002519 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002520};
2521
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002522ContourIter::ContourIter(const SkPathRef& pathRef) {
2523 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002524 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002525 fCurrPt = pathRef.points();
2526 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002527 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002528 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002529 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002530 this->next();
2531}
2532
2533void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002534 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002535 fDone = true;
2536 }
2537 if (fDone) {
2538 return;
2539 }
2540
2541 // skip pts of prev contour
2542 fCurrPt += fCurrPtCount;
2543
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002544 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002545 int ptCount = 1; // moveTo
2546 const uint8_t* verbs = fCurrVerb;
2547
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002548 for (--verbs; verbs > fStopVerbs; --verbs) {
2549 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002550 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002551 goto CONTOUR_END;
2552 case SkPath::kLine_Verb:
2553 ptCount += 1;
2554 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002555 case SkPath::kConic_Verb:
2556 fCurrConicWeight += 1;
2557 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002558 case SkPath::kQuad_Verb:
2559 ptCount += 2;
2560 break;
2561 case SkPath::kCubic_Verb:
2562 ptCount += 3;
2563 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002564 case SkPath::kClose_Verb:
2565 break;
2566 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002567 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002568 break;
2569 }
2570 }
2571CONTOUR_END:
2572 fCurrPtCount = ptCount;
2573 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002574 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002575}
2576
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002577// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002578static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002579 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2580 // We may get 0 when the above subtracts underflow. We expect this to be
2581 // very rare and lazily promote to double.
2582 if (0 == cross) {
2583 double p0x = SkScalarToDouble(p0.fX);
2584 double p0y = SkScalarToDouble(p0.fY);
2585
2586 double p1x = SkScalarToDouble(p1.fX);
2587 double p1y = SkScalarToDouble(p1.fY);
2588
2589 double p2x = SkScalarToDouble(p2.fX);
2590 double p2y = SkScalarToDouble(p2.fY);
2591
2592 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2593 (p1y - p0y) * (p2x - p0x));
2594
2595 }
2596 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002597}
2598
reed@google.comc1ea60a2012-01-31 15:15:36 +00002599// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002600static int find_max_y(const SkPoint pts[], int count) {
2601 SkASSERT(count > 0);
2602 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002603 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002604 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002605 SkScalar y = pts[i].fY;
2606 if (y > max) {
2607 max = y;
2608 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002609 }
2610 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002611 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002612}
2613
reed@google.comcabaf1d2012-01-11 21:03:05 +00002614static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2615 int i = index;
2616 for (;;) {
2617 i = (i + inc) % n;
2618 if (i == index) { // we wrapped around, so abort
2619 break;
2620 }
2621 if (pts[index] != pts[i]) { // found a different point, success!
2622 break;
2623 }
2624 }
2625 return i;
2626}
2627
reed@google.comc1ea60a2012-01-31 15:15:36 +00002628/**
2629 * Starting at index, and moving forward (incrementing), find the xmin and
2630 * xmax of the contiguous points that have the same Y.
2631 */
2632static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2633 int* maxIndexPtr) {
2634 const SkScalar y = pts[index].fY;
2635 SkScalar min = pts[index].fX;
2636 SkScalar max = min;
2637 int minIndex = index;
2638 int maxIndex = index;
2639 for (int i = index + 1; i < n; ++i) {
2640 if (pts[i].fY != y) {
2641 break;
2642 }
2643 SkScalar x = pts[i].fX;
2644 if (x < min) {
2645 min = x;
2646 minIndex = i;
2647 } else if (x > max) {
2648 max = x;
2649 maxIndex = i;
2650 }
2651 }
2652 *maxIndexPtr = maxIndex;
2653 return minIndex;
2654}
2655
reed026beb52015-06-10 14:23:15 -07002656static void crossToDir(SkScalar cross, SkPathPriv::FirstDirection* dir) {
2657 *dir = cross > 0 ? SkPathPriv::kCW_FirstDirection : SkPathPriv::kCCW_FirstDirection;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002658}
2659
reed@google.comac8543f2012-01-30 20:51:25 +00002660/*
2661 * We loop through all contours, and keep the computed cross-product of the
2662 * contour that contained the global y-max. If we just look at the first
2663 * contour, we may find one that is wound the opposite way (correctly) since
2664 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2665 * that is outer most (or at least has the global y-max) before we can consider
2666 * its cross product.
2667 */
reed026beb52015-06-10 14:23:15 -07002668bool SkPathPriv::CheapComputeFirstDirection(const SkPath& path, FirstDirection* dir) {
herb9f4dbca2015-09-28 11:05:47 -07002669 if (kUnknown_FirstDirection != path.fFirstDirection.load()) {
2670 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002671 return true;
2672 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002673
2674 // don't want to pay the cost for computing this if it
2675 // is unknown, so we don't call isConvex()
reed026beb52015-06-10 14:23:15 -07002676 if (SkPath::kConvex_Convexity == path.getConvexityOrUnknown()) {
2677 SkASSERT(kUnknown_FirstDirection == path.fFirstDirection);
herb9f4dbca2015-09-28 11:05:47 -07002678 *dir = static_cast<FirstDirection>(path.fFirstDirection.load());
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002679 return false;
2680 }
reed@google.com69a99432012-01-10 18:00:10 +00002681
reed026beb52015-06-10 14:23:15 -07002682 ContourIter iter(*path.fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002683
reed@google.comac8543f2012-01-30 20:51:25 +00002684 // initialize with our logical y-min
reed026beb52015-06-10 14:23:15 -07002685 SkScalar ymax = path.getBounds().fTop;
reed@google.comac8543f2012-01-30 20:51:25 +00002686 SkScalar ymaxCross = 0;
2687
reed@google.com69a99432012-01-10 18:00:10 +00002688 for (; !iter.done(); iter.next()) {
2689 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002690 if (n < 3) {
2691 continue;
2692 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002693
reed@google.comcabaf1d2012-01-11 21:03:05 +00002694 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002695 SkScalar cross = 0;
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002696 int index = find_max_y(pts, n);
2697 if (pts[index].fY < ymax) {
2698 continue;
2699 }
2700
2701 // If there is more than 1 distinct point at the y-max, we take the
2702 // x-min and x-max of them and just subtract to compute the dir.
2703 if (pts[(index + 1) % n].fY == pts[index].fY) {
2704 int maxIndex;
2705 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
2706 if (minIndex == maxIndex) {
2707 goto TRY_CROSSPROD;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002708 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002709 SkASSERT(pts[minIndex].fY == pts[index].fY);
2710 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2711 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2712 // we just subtract the indices, and let that auto-convert to
2713 // SkScalar, since we just want - or + to signal the direction.
2714 cross = minIndex - maxIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002715 } else {
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002716 TRY_CROSSPROD:
2717 // Find a next and prev index to use for the cross-product test,
2718 // but we try to find pts that form non-zero vectors from pts[index]
2719 //
2720 // Its possible that we can't find two non-degenerate vectors, so
2721 // we have to guard our search (e.g. all the pts could be in the
2722 // same place).
2723
2724 // we pass n - 1 instead of -1 so we don't foul up % operator by
2725 // passing it a negative LH argument.
2726 int prev = find_diff_pt(pts, index, n, n - 1);
2727 if (prev == index) {
2728 // completely degenerate, skip to next contour
reed@google.comac8543f2012-01-30 20:51:25 +00002729 continue;
2730 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002731 int next = find_diff_pt(pts, index, n, 1);
2732 SkASSERT(next != index);
2733 cross = cross_prod(pts[prev], pts[index], pts[next]);
2734 // if we get a zero and the points are horizontal, then we look at the spread in
2735 // x-direction. We really should continue to walk away from the degeneracy until
2736 // there is a divergence.
2737 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
2738 // construct the subtract so we get the correct Direction below
2739 cross = pts[index].fX - pts[next].fX;
reed@google.com188bfcf2012-01-17 18:26:38 +00002740 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002741 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002742
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002743 if (cross) {
2744 // record our best guess so far
2745 ymax = pts[index].fY;
2746 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002747 }
2748 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002749 if (ymaxCross) {
2750 crossToDir(ymaxCross, dir);
reed026beb52015-06-10 14:23:15 -07002751 path.fFirstDirection = *dir;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002752 return true;
2753 } else {
2754 return false;
2755 }
reed@google.comac8543f2012-01-30 20:51:25 +00002756}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002757
2758///////////////////////////////////////////////////////////////////////////////
2759
caryclark9aacd902015-12-14 08:38:09 -08002760static bool between(SkScalar a, SkScalar b, SkScalar c) {
2761 SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)
2762 || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c)));
2763 return (a - b) * (c - b) <= 0;
2764}
2765
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002766static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2767 SkScalar t) {
2768 SkScalar A = c3 + 3*(c1 - c2) - c0;
2769 SkScalar B = 3*(c2 - c1 - c1 + c0);
2770 SkScalar C = 3*(c1 - c0);
2771 SkScalar D = c0;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002772 return poly_eval(A, B, C, D, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002773}
2774
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002775template <size_t N> static void find_minmax(const SkPoint pts[],
2776 SkScalar* minPtr, SkScalar* maxPtr) {
2777 SkScalar min, max;
2778 min = max = pts[0].fX;
2779 for (size_t i = 1; i < N; ++i) {
2780 min = SkMinScalar(min, pts[i].fX);
2781 max = SkMaxScalar(max, pts[i].fX);
2782 }
2783 *minPtr = min;
2784 *maxPtr = max;
2785}
2786
caryclark9cb5d752015-12-18 04:35:24 -08002787static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) {
2788 if (start.fY == end.fY) {
2789 return between(start.fX, x, end.fX) && x != end.fX;
2790 } else {
2791 return x == start.fX && y == start.fY;
2792 }
2793}
2794
caryclark9aacd902015-12-14 08:38:09 -08002795static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
caryclark9cb5d752015-12-18 04:35:24 -08002796 SkScalar y0 = pts[0].fY;
2797 SkScalar y3 = pts[3].fY;
2798
2799 int dir = 1;
2800 if (y0 > y3) {
2801 SkTSwap(y0, y3);
2802 dir = -1;
2803 }
2804 if (y < y0 || y > y3) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805 return 0;
2806 }
caryclark9cb5d752015-12-18 04:35:24 -08002807 if (checkOnCurve(x, y, pts[0], pts[3])) {
2808 *onCurveCount += 1;
caryclark9aacd902015-12-14 08:38:09 -08002809 return 0;
2810 }
caryclark9cb5d752015-12-18 04:35:24 -08002811 if (y == y3) {
2812 return 0;
2813 }
2814
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002815 // quickreject or quickaccept
2816 SkScalar min, max;
2817 find_minmax<4>(pts, &min, &max);
2818 if (x < min) {
2819 return 0;
2820 }
2821 if (x > max) {
2822 return dir;
2823 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002824
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 // compute the actual x(t) value
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002826 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07002827 if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07002828 return 0;
mbarbella276e6332016-05-31 14:44:01 -07002829 }
commit-bot@chromium.orga1a097e2013-11-14 16:53:22 +00002830 SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
caryclark9aacd902015-12-14 08:38:09 -08002831 if (SkScalarNearlyEqual(xt, x)) {
2832 if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points
2833 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002834 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002835 }
2836 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002837 return xt < x ? dir : 0;
2838}
2839
caryclark9aacd902015-12-14 08:38:09 -08002840static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 SkPoint dst[10];
2842 int n = SkChopCubicAtYExtrema(pts, dst);
2843 int w = 0;
2844 for (int i = 0; i <= n; ++i) {
caryclark9aacd902015-12-14 08:38:09 -08002845 w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002846 }
2847 return w;
2848}
2849
caryclark9aacd902015-12-14 08:38:09 -08002850static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) {
2851 SkASSERT(src);
2852 SkASSERT(t >= 0 && t <= 1);
2853 SkScalar src2w = src[2] * w;
2854 SkScalar C = src[0];
2855 SkScalar A = src[4] - 2 * src2w + C;
2856 SkScalar B = 2 * (src2w - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002857 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002858}
2859
2860
2861static double conic_eval_denominator(SkScalar w, SkScalar t) {
2862 SkScalar B = 2 * (w - 1);
2863 SkScalar C = 1;
2864 SkScalar A = -B;
Mike Reeda99b6ce2017-02-04 11:04:26 -05002865 return poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08002866}
2867
2868static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) {
2869 const SkPoint* pts = conic.fPts;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002870 SkScalar y0 = pts[0].fY;
2871 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002872
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002873 int dir = 1;
2874 if (y0 > y2) {
2875 SkTSwap(y0, y2);
2876 dir = -1;
2877 }
caryclark9aacd902015-12-14 08:38:09 -08002878 if (y < y0 || y > y2) {
2879 return 0;
2880 }
caryclark9cb5d752015-12-18 04:35:24 -08002881 if (checkOnCurve(x, y, pts[0], pts[2])) {
2882 *onCurveCount += 1;
2883 return 0;
2884 }
caryclark9aacd902015-12-14 08:38:09 -08002885 if (y == y2) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 return 0;
2887 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002888
caryclark9aacd902015-12-14 08:38:09 -08002889 SkScalar roots[2];
2890 SkScalar A = pts[2].fY;
2891 SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y;
2892 SkScalar C = pts[0].fY;
2893 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
2894 B -= C; // B = b*w - w * yCept + yCept - a
2895 C -= y;
2896 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
2897 SkASSERT(n <= 1);
2898 SkScalar xt;
2899 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002900 // zero roots are returned only when y0 == y
2901 // Need [0] if dir == 1
2902 // and [2] if dir == -1
2903 xt = pts[1 - dir].fX;
caryclark9aacd902015-12-14 08:38:09 -08002904 } else {
2905 SkScalar t = roots[0];
2906 xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t);
2907 }
2908 if (SkScalarNearlyEqual(xt, x)) {
2909 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2910 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002911 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002912 }
2913 }
2914 return xt < x ? dir : 0;
2915}
2916
2917static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2918 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2919 if (y0 == y1) {
2920 return true;
2921 }
2922 if (y0 < y1) {
2923 return y1 <= y2;
2924 } else {
2925 return y1 >= y2;
2926 }
2927}
2928
2929static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight,
2930 int* onCurveCount) {
2931 SkConic conic(pts, weight);
caryclark9aacd902015-12-14 08:38:09 -08002932 SkConic chopped[2];
caryclark1e1e5092016-01-06 08:27:44 -08002933 // If the data points are very large, the conic may not be monotonic but may also
2934 // fail to chop. Then, the chopper does not split the original conic in two.
caryclarke114dd62016-01-06 08:15:44 -08002935 bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped);
2936 int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount);
2937 if (!isMono) {
caryclark9aacd902015-12-14 08:38:09 -08002938 w += winding_mono_conic(chopped[1], x, y, onCurveCount);
2939 }
2940 return w;
2941}
2942
2943static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
2944 SkScalar y0 = pts[0].fY;
2945 SkScalar y2 = pts[2].fY;
2946
2947 int dir = 1;
2948 if (y0 > y2) {
2949 SkTSwap(y0, y2);
2950 dir = -1;
2951 }
2952 if (y < y0 || y > y2) {
2953 return 0;
2954 }
caryclark9cb5d752015-12-18 04:35:24 -08002955 if (checkOnCurve(x, y, pts[0], pts[2])) {
2956 *onCurveCount += 1;
2957 return 0;
2958 }
caryclark9aacd902015-12-14 08:38:09 -08002959 if (y == y2) {
caryclark9aacd902015-12-14 08:38:09 -08002960 return 0;
2961 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002962 // bounds check on X (not required. is it faster?)
2963#if 0
2964 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2965 return 0;
2966 }
2967#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002968
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002969 SkScalar roots[2];
2970 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2971 2 * (pts[1].fY - pts[0].fY),
2972 pts[0].fY - y,
2973 roots);
2974 SkASSERT(n <= 1);
2975 SkScalar xt;
2976 if (0 == n) {
caryclark9cb5d752015-12-18 04:35:24 -08002977 // zero roots are returned only when y0 == y
2978 // Need [0] if dir == 1
2979 // and [2] if dir == -1
2980 xt = pts[1 - dir].fX;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002981 } else {
2982 SkScalar t = roots[0];
2983 SkScalar C = pts[0].fX;
2984 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2985 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05002986 xt = poly_eval(A, B, C, t);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002987 }
caryclark9aacd902015-12-14 08:38:09 -08002988 if (SkScalarNearlyEqual(xt, x)) {
2989 if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points
2990 *onCurveCount += 1;
caryclark9cb5d752015-12-18 04:35:24 -08002991 return 0;
caryclark9aacd902015-12-14 08:38:09 -08002992 }
2993 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002994 return xt < x ? dir : 0;
2995}
2996
caryclark9aacd902015-12-14 08:38:09 -08002997static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002998 SkPoint dst[5];
2999 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003000
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003001 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
3002 n = SkChopQuadAtYExtrema(pts, dst);
3003 pts = dst;
3004 }
caryclark9aacd902015-12-14 08:38:09 -08003005 int w = winding_mono_quad(pts, x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003006 if (n > 0) {
caryclark9aacd902015-12-14 08:38:09 -08003007 w += winding_mono_quad(&pts[2], x, y, onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003008 }
3009 return w;
3010}
3011
caryclark9aacd902015-12-14 08:38:09 -08003012static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003013 SkScalar x0 = pts[0].fX;
3014 SkScalar y0 = pts[0].fY;
3015 SkScalar x1 = pts[1].fX;
3016 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003017
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003018 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003019
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003020 int dir = 1;
3021 if (y0 > y1) {
3022 SkTSwap(y0, y1);
3023 dir = -1;
3024 }
caryclark9aacd902015-12-14 08:38:09 -08003025 if (y < y0 || y > y1) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003026 return 0;
3027 }
caryclark9cb5d752015-12-18 04:35:24 -08003028 if (checkOnCurve(x, y, pts[0], pts[1])) {
3029 *onCurveCount += 1;
3030 return 0;
3031 }
3032 if (y == y1) {
caryclark9aacd902015-12-14 08:38:09 -08003033 return 0;
3034 }
Mike Reeda99b6ce2017-02-04 11:04:26 -05003035 SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003036
caryclark9aacd902015-12-14 08:38:09 -08003037 if (!cross) {
fsc91065d2015-12-17 09:03:27 -08003038 // zero cross means the point is on the line, and since the case where
3039 // y of the query point is at the end point is handled above, we can be
3040 // sure that we're on the line (excluding the end point) here
caryclark9cb5d752015-12-18 04:35:24 -08003041 if (x != x1 || y != pts[1].fY) {
3042 *onCurveCount += 1;
3043 }
caryclark9aacd902015-12-14 08:38:09 -08003044 dir = 0;
3045 } else if (SkScalarSignAsInt(cross) == dir) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003046 dir = 0;
3047 }
3048 return dir;
3049}
3050
caryclark9aacd902015-12-14 08:38:09 -08003051static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y,
3052 SkTDArray<SkVector>* tangents) {
3053 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)
3054 && !between(pts[2].fY, y, pts[3].fY)) {
3055 return;
3056 }
3057 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)
3058 && !between(pts[2].fX, x, pts[3].fX)) {
3059 return;
3060 }
3061 SkPoint dst[10];
3062 int n = SkChopCubicAtYExtrema(pts, dst);
3063 for (int i = 0; i <= n; ++i) {
3064 SkPoint* c = &dst[i * 3];
3065 SkScalar t;
mbarbella276e6332016-05-31 14:44:01 -07003066 if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) {
mbarbella99600d02016-06-01 15:39:47 -07003067 continue;
mbarbella276e6332016-05-31 14:44:01 -07003068 }
caryclark9aacd902015-12-14 08:38:09 -08003069 SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t);
3070 if (!SkScalarNearlyEqual(x, xt)) {
3071 continue;
3072 }
3073 SkVector tangent;
3074 SkEvalCubicAt(c, t, nullptr, &tangent, nullptr);
3075 tangents->push(tangent);
3076 }
3077}
3078
3079static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w,
3080 SkTDArray<SkVector>* tangents) {
3081 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3082 return;
3083 }
3084 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3085 return;
3086 }
3087 SkScalar roots[2];
3088 SkScalar A = pts[2].fY;
3089 SkScalar B = pts[1].fY * w - y * w + y;
3090 SkScalar C = pts[0].fY;
3091 A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept)
3092 B -= C; // B = b*w - w * yCept + yCept - a
3093 C -= y;
3094 int n = SkFindUnitQuadRoots(A, 2 * B, C, roots);
3095 for (int index = 0; index < n; ++index) {
3096 SkScalar t = roots[index];
3097 SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t);
3098 if (!SkScalarNearlyEqual(x, xt)) {
3099 continue;
3100 }
3101 SkConic conic(pts, w);
3102 tangents->push(conic.evalTangentAt(t));
3103 }
3104}
3105
3106static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y,
3107 SkTDArray<SkVector>* tangents) {
3108 if (!between(pts[0].fY, y, pts[1].fY) && !between(pts[1].fY, y, pts[2].fY)) {
3109 return;
3110 }
3111 if (!between(pts[0].fX, x, pts[1].fX) && !between(pts[1].fX, x, pts[2].fX)) {
3112 return;
3113 }
3114 SkScalar roots[2];
3115 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
3116 2 * (pts[1].fY - pts[0].fY),
3117 pts[0].fY - y,
3118 roots);
3119 for (int index = 0; index < n; ++index) {
3120 SkScalar t = roots[index];
3121 SkScalar C = pts[0].fX;
3122 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
3123 SkScalar B = 2 * (pts[1].fX - C);
Mike Reeda99b6ce2017-02-04 11:04:26 -05003124 SkScalar xt = poly_eval(A, B, C, t);
caryclark9aacd902015-12-14 08:38:09 -08003125 if (!SkScalarNearlyEqual(x, xt)) {
3126 continue;
3127 }
3128 tangents->push(SkEvalQuadTangentAt(pts, t));
3129 }
3130}
3131
3132static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y,
3133 SkTDArray<SkVector>* tangents) {
3134 SkScalar y0 = pts[0].fY;
3135 SkScalar y1 = pts[1].fY;
3136 if (!between(y0, y, y1)) {
3137 return;
3138 }
3139 SkScalar x0 = pts[0].fX;
3140 SkScalar x1 = pts[1].fX;
3141 if (!between(x0, x, x1)) {
3142 return;
3143 }
3144 SkScalar dx = x1 - x0;
3145 SkScalar dy = y1 - y0;
3146 if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) {
3147 return;
3148 }
3149 SkVector v;
3150 v.set(dx, dy);
3151 tangents->push(v);
3152}
3153
reed@google.com4db592c2013-10-30 17:39:43 +00003154static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3155 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3156}
3157
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003158bool SkPath::contains(SkScalar x, SkScalar y) const {
3159 bool isInverse = this->isInverseFillType();
3160 if (this->isEmpty()) {
3161 return isInverse;
3162 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003163
reed@google.com4db592c2013-10-30 17:39:43 +00003164 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003165 return isInverse;
3166 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003167
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003168 SkPath::Iter iter(*this, true);
3169 bool done = false;
3170 int w = 0;
caryclark9aacd902015-12-14 08:38:09 -08003171 int onCurveCount = 0;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003172 do {
3173 SkPoint pts[4];
3174 switch (iter.next(pts, false)) {
3175 case SkPath::kMove_Verb:
3176 case SkPath::kClose_Verb:
3177 break;
3178 case SkPath::kLine_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003179 w += winding_line(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003180 break;
3181 case SkPath::kQuad_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003182 w += winding_quad(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003183 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003184 case SkPath::kConic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003185 w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount);
reed@google.com277c3f82013-05-31 15:17:50 +00003186 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003187 case SkPath::kCubic_Verb:
caryclark9aacd902015-12-14 08:38:09 -08003188 w += winding_cubic(pts, x, y, &onCurveCount);
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003189 break;
3190 case SkPath::kDone_Verb:
3191 done = true;
3192 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003193 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003194 } while (!done);
caryclark9aacd902015-12-14 08:38:09 -08003195 bool evenOddFill = SkPath::kEvenOdd_FillType == this->getFillType()
3196 || SkPath::kInverseEvenOdd_FillType == this->getFillType();
3197 if (evenOddFill) {
3198 w &= 1;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003199 }
caryclark9aacd902015-12-14 08:38:09 -08003200 if (w) {
3201 return !isInverse;
3202 }
3203 if (onCurveCount <= 1) {
3204 return SkToBool(onCurveCount) ^ isInverse;
3205 }
3206 if ((onCurveCount & 1) || evenOddFill) {
3207 return SkToBool(onCurveCount & 1) ^ isInverse;
3208 }
halcanary9d524f22016-03-29 09:03:52 -07003209 // If the point touches an even number of curves, and the fill is winding, check for
caryclark9aacd902015-12-14 08:38:09 -08003210 // coincidence. Count coincidence as places where the on curve points have identical tangents.
3211 iter.setPath(*this, true);
caryclark9cb5d752015-12-18 04:35:24 -08003212 done = false;
caryclark9aacd902015-12-14 08:38:09 -08003213 SkTDArray<SkVector> tangents;
3214 do {
3215 SkPoint pts[4];
3216 int oldCount = tangents.count();
3217 switch (iter.next(pts, false)) {
3218 case SkPath::kMove_Verb:
3219 case SkPath::kClose_Verb:
3220 break;
3221 case SkPath::kLine_Verb:
3222 tangent_line(pts, x, y, &tangents);
3223 break;
3224 case SkPath::kQuad_Verb:
3225 tangent_quad(pts, x, y, &tangents);
3226 break;
3227 case SkPath::kConic_Verb:
3228 tangent_conic(pts, x, y, iter.conicWeight(), &tangents);
3229 break;
3230 case SkPath::kCubic_Verb:
3231 tangent_cubic(pts, x, y, &tangents);
3232 break;
3233 case SkPath::kDone_Verb:
3234 done = true;
3235 break;
3236 }
3237 if (tangents.count() > oldCount) {
3238 int last = tangents.count() - 1;
3239 const SkVector& tangent = tangents[last];
Cary Clarkdf429f32017-11-08 11:44:31 -05003240 if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) {
caryclark9aacd902015-12-14 08:38:09 -08003241 tangents.remove(last);
3242 } else {
3243 for (int index = 0; index < last; ++index) {
3244 const SkVector& test = tangents[index];
3245 if (SkScalarNearlyZero(test.cross(tangent))
caryclark9cb5d752015-12-18 04:35:24 -08003246 && SkScalarSignAsInt(tangent.fX * test.fX) <= 0
3247 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) {
caryclark9aacd902015-12-14 08:38:09 -08003248 tangents.remove(last);
3249 tangents.removeShuffle(index);
3250 break;
3251 }
3252 }
3253 }
3254 }
3255 } while (!done);
3256 return SkToBool(tangents.count()) ^ isInverse;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003257}
fmalitaaa0df4e2015-12-01 09:13:23 -08003258
3259int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
3260 SkScalar w, SkPoint pts[], int pow2) {
3261 const SkConic conic(p0, p1, p2, w);
3262 return conic.chopIntoQuadsPOW2(pts, pow2);
3263}
bsalomonedc743a2016-06-01 09:42:31 -07003264
3265bool SkPathPriv::IsSimpleClosedRect(const SkPath& path, SkRect* rect, SkPath::Direction* direction,
3266 unsigned* start) {
3267 if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) {
3268 return false;
3269 }
3270 SkPath::RawIter iter(path);
3271 SkPoint verbPts[4];
3272 SkPath::Verb v;
3273 SkPoint rectPts[5];
3274 int rectPtCnt = 0;
3275 while ((v = iter.next(verbPts)) != SkPath::kDone_Verb) {
3276 switch (v) {
3277 case SkPath::kMove_Verb:
3278 if (0 != rectPtCnt) {
3279 return false;
3280 }
3281 rectPts[0] = verbPts[0];
3282 ++rectPtCnt;
3283 break;
3284 case SkPath::kLine_Verb:
3285 if (5 == rectPtCnt) {
3286 return false;
3287 }
3288 rectPts[rectPtCnt] = verbPts[1];
3289 ++rectPtCnt;
3290 break;
3291 case SkPath::kClose_Verb:
3292 if (4 == rectPtCnt) {
3293 rectPts[4] = rectPts[0];
3294 rectPtCnt = 5;
3295 }
3296 break;
3297 default:
3298 return false;
3299 }
3300 }
3301 if (rectPtCnt < 5) {
3302 return false;
3303 }
3304 if (rectPts[0] != rectPts[4]) {
3305 return false;
3306 }
bsalomon057ae8a2016-07-24 05:37:26 -07003307 // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge (
3308 // and pts 1 and 2 the opposite vertical or horizontal edge).
3309 bool vec03IsVertical;
3310 if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX &&
3311 rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) {
3312 // Make sure it has non-zero width and height
3313 if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) {
bsalomonedc743a2016-06-01 09:42:31 -07003314 return false;
3315 }
bsalomon057ae8a2016-07-24 05:37:26 -07003316 vec03IsVertical = true;
3317 } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY &&
3318 rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) {
3319 // Make sure it has non-zero width and height
3320 if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) {
3321 return false;
3322 }
3323 vec03IsVertical = false;
3324 } else {
bsalomonedc743a2016-06-01 09:42:31 -07003325 return false;
3326 }
bsalomon057ae8a2016-07-24 05:37:26 -07003327 // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit
3328 // set if it is on the bottom edge.
3329 unsigned sortFlags =
3330 ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) |
3331 ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10);
3332 switch (sortFlags) {
3333 case 0b00:
3334 rect->set(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY);
3335 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3336 *start = 0;
3337 break;
3338 case 0b01:
3339 rect->set(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY);
3340 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3341 *start = 1;
3342 break;
3343 case 0b10:
3344 rect->set(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY);
3345 *direction = vec03IsVertical ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
3346 *start = 3;
3347 break;
3348 case 0b11:
3349 rect->set(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY);
3350 *direction = vec03IsVertical ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
3351 *start = 2;
3352 break;
bsalomonedc743a2016-06-01 09:42:31 -07003353 }
bsalomonedc743a2016-06-01 09:42:31 -07003354 return true;
3355}
bsalomon21af9ca2016-08-25 12:29:23 -07003356
3357void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle,
3358 SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) {
3359 SkASSERT(!oval.isEmpty());
3360 SkASSERT(sweepAngle);
3361
3362 path->reset();
3363 path->setIsVolatile(true);
3364 path->setFillType(SkPath::kWinding_FillType);
3365 if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) {
3366 path->addOval(oval);
3367 return;
3368 }
3369 if (useCenter) {
3370 path->moveTo(oval.centerX(), oval.centerY());
3371 }
3372 // Arc to mods at 360 and drawArc is not supposed to.
3373 bool forceMoveTo = !useCenter;
3374 while (sweepAngle <= -360.f) {
3375 path->arcTo(oval, startAngle, -180.f, forceMoveTo);
3376 startAngle -= 180.f;
3377 path->arcTo(oval, startAngle, -180.f, false);
3378 startAngle -= 180.f;
3379 forceMoveTo = false;
3380 sweepAngle += 360.f;
3381 }
3382 while (sweepAngle >= 360.f) {
3383 path->arcTo(oval, startAngle, 180.f, forceMoveTo);
3384 startAngle += 180.f;
3385 path->arcTo(oval, startAngle, 180.f, false);
3386 startAngle += 180.f;
3387 forceMoveTo = false;
3388 sweepAngle -= 360.f;
3389 }
3390 path->arcTo(oval, startAngle, sweepAngle, forceMoveTo);
3391 if (useCenter) {
3392 path->close();
3393 }
3394}
Mike Reed0d7dac82017-02-02 17:45:56 -08003395
3396///////////////////////////////////////////////////////////////////////////////////////////////////
3397#include "SkNx.h"
3398
3399static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) {
3400 SkScalar ts[2];
3401 int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts);
3402 n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]);
3403 SkASSERT(n >= 0 && n <= 2);
3404 for (int i = 0; i < n; ++i) {
3405 extremas[i] = SkEvalQuadAt(src, ts[i]);
3406 }
3407 extremas[n] = src[2];
3408 return n + 1;
3409}
3410
3411static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) {
3412 SkConic conic(src[0], src[1], src[2], w);
3413 SkScalar ts[2];
3414 int n = conic.findXExtrema(ts);
3415 n += conic.findYExtrema(&ts[n]);
3416 SkASSERT(n >= 0 && n <= 2);
3417 for (int i = 0; i < n; ++i) {
3418 extremas[i] = conic.evalAt(ts[i]);
3419 }
3420 extremas[n] = src[2];
3421 return n + 1;
3422}
3423
3424static int compute_cubic_extremas(const SkPoint src[3], SkPoint extremas[5]) {
3425 SkScalar ts[4];
3426 int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts);
3427 n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]);
3428 SkASSERT(n >= 0 && n <= 4);
3429 for (int i = 0; i < n; ++i) {
3430 SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr);
3431 }
3432 extremas[n] = src[3];
3433 return n + 1;
3434}
3435
Mike Reed8d3196b2017-02-03 11:34:13 -05003436SkRect SkPath::computeTightBounds() const {
3437 if (0 == this->countVerbs()) {
3438 return SkRect::MakeEmpty();
Mike Reed0d7dac82017-02-02 17:45:56 -08003439 }
3440
Mike Reed8d3196b2017-02-03 11:34:13 -05003441 if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) {
3442 return this->getBounds();
Mike Reed0d7dac82017-02-02 17:45:56 -08003443 }
Mike Kleinb9b5de52017-09-27 13:26:22 -04003444
Mike Reed0d7dac82017-02-02 17:45:56 -08003445 SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1
3446 SkPoint pts[4];
Mike Reed8d3196b2017-02-03 11:34:13 -05003447 SkPath::RawIter iter(*this);
Mike Reed0d7dac82017-02-02 17:45:56 -08003448
3449 // initial with the first MoveTo, so we don't have to check inside the switch
3450 Sk2s min, max;
Mike Reed8d3196b2017-02-03 11:34:13 -05003451 min = max = from_point(this->getPoint(0));
Mike Reed0d7dac82017-02-02 17:45:56 -08003452 for (;;) {
3453 int count = 0;
3454 switch (iter.next(pts)) {
3455 case SkPath::kMove_Verb:
3456 extremas[0] = pts[0];
3457 count = 1;
3458 break;
3459 case SkPath::kLine_Verb:
3460 extremas[0] = pts[1];
3461 count = 1;
3462 break;
3463 case SkPath::kQuad_Verb:
3464 count = compute_quad_extremas(pts, extremas);
3465 break;
3466 case SkPath::kConic_Verb:
3467 count = compute_conic_extremas(pts, iter.conicWeight(), extremas);
3468 break;
3469 case SkPath::kCubic_Verb:
3470 count = compute_cubic_extremas(pts, extremas);
3471 break;
3472 case SkPath::kClose_Verb:
3473 break;
3474 case SkPath::kDone_Verb:
3475 goto DONE;
3476 }
3477 for (int i = 0; i < count; ++i) {
3478 Sk2s tmp = from_point(extremas[i]);
3479 min = Sk2s::Min(min, tmp);
3480 max = Sk2s::Max(max, tmp);
3481 }
3482 }
3483DONE:
Mike Reed8d3196b2017-02-03 11:34:13 -05003484 SkRect bounds;
3485 min.store((SkPoint*)&bounds.fLeft);
3486 max.store((SkPoint*)&bounds.fRight);
3487 return bounds;
Mike Reed0d7dac82017-02-02 17:45:56 -08003488}
Cary Clarkdf429f32017-11-08 11:44:31 -05003489
3490bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
3491 return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2);
3492}
3493
3494bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
3495 const SkPoint& p3, bool exact) {
3496 return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3497 SkPointPriv::EqualsWithinTolerance(p2, p3);
3498}
3499
3500bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
3501 const SkPoint& p3, const SkPoint& p4, bool exact) {
3502 return exact ? p1 == p2 && p2 == p3 && p3 == p4 :
3503 SkPointPriv::EqualsWithinTolerance(p1, p2) &&
3504 SkPointPriv::EqualsWithinTolerance(p2, p3) &&
3505 SkPointPriv::EqualsWithinTolerance(p3, p4);
3506}